Overhaul implementations and documentation.

This commit is contained in:
Ray Koopa 2020-07-01 02:09:36 +02:00
parent 8b92e6a8be
commit 5321dfb49a
777 changed files with 7857 additions and 5718 deletions

7
.gitattributes vendored
View File

@ -1 +1,8 @@
*.dat filter=lfs diff=lfs merge=lfs -text
*.img filter=lfs diff=lfs merge=lfs -text
*.pxl filter=lfs diff=lfs merge=lfs -text
*.pxs filter=lfs diff=lfs merge=lfs -text
*.wgt filter=lfs diff=lfs merge=lfs -text
*.wsc filter=lfs diff=lfs merge=lfs -text
*.wwp filter=lfs diff=lfs merge=lfs -text
src/test/Syroot.Worms.Armageddon.Test/Files/Schemes/WA/Wsdb/dump.csv filter=lfs diff=lfs merge=lfs -text

8
src/010editor/Common.bt Normal file
View File

@ -0,0 +1,8 @@
void FAlign(byte alignment)
{
local int bytesToSkip <hidden=true> = (-FTell() % alignment + alignment) % alignment;
while (bytesToSkip--)
{
byte padding <fgcolor=0x808080, hidden=true>;
}
}

18
src/010editor/IMG.bt Normal file
View File

@ -0,0 +1,18 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: IMG.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms Armageddon and Worms World Party image files.
// Category: Worms
// File Mask: *.img
// ID Bytes:
// History:
// 0.1.0 2020-06-29 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
Image image <open=true>;

74
src/010editor/Image.bt Normal file
View File

@ -0,0 +1,74 @@
#include "Common.bt"
typedef struct(char description, char align)
{
uint signature <format=hex>;
if (signature != 0x1A474D49) // "IMG\x1A"
{
Warning("Invalid IMG signature.");
Exit(-1);
}
uint fileSize;
if (description)
string description;
ubyte bpp;
enum <ubyte> Flags
{
IMG_COMPRESSED = 1 << 6,
IMG_PALETTIZED = 1 << 7
} flags;
if (flags & IMG_PALETTIZED)
{
ushort colorCount;
struct Color
{
ubyte r;
ubyte g;
ubyte b;
} colors[colorCount];
}
ushort width;
ushort height;
if (align)
FAlign(4);
if (flags & IMG_COMPRESSED)
char data[GetCompressedImgSize()];
else
char data[bpp / 8.0 * width * height];
} Image;
int GetCompressedImgSize()
{
local int pos = FTell();
local int start = pos;
local int cmd;
while ((cmd = ReadByte(pos++)) != -1)
{
// Read a byte.
if ((cmd & 0x80) != 0)
{
local int arg1 = cmd >> 3 & 0b1111; // bits 2-5
local int arg2 = ReadByte(pos++);
if (arg2 == -1)
break;
// Arg2 = bits 6-16
arg2 = ((cmd << 8) | arg2) & 0x7FF;
if (arg1 == 0)
{
// Command: 0x80 0x00
if (arg2 == 0)
break;
if (ReadByte(pos++) == -1)
break;
}
}
}
return pos - start;
}

20
src/010editor/OW_PAL.bt Normal file
View File

@ -0,0 +1,20 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: OW_PAL.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Online Worms PAL color palettes.
// Category: Worms
// File Mask: *.pal
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
LittleEndian();
struct Color
{
ubyte r, g, b;
} colors[256];

View File

@ -0,0 +1,44 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: W2_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms 2 land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
int unknown;
Image foregroundImage(true, false);
Image collisionImage(true, false);
Image backgroundImage(true, false);
Image unknownImage(true, false);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,43 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WA3.0_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms Armageddon (older than 3.6.28.0) land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, false);
Image collisionImage(false, false);
Image backgroundImage(false, false);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,44 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WA3.6_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms Armageddon (3.6.28.0 and newer) land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int holeCount;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, false);
Image collisionImage(false, false);
Image backgroundImage(false, false);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,44 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WWPA_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms World Party Aqua land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1B444E4C) // "LND\x1B"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int holeCount;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, true);
Image collisionImage(false, true);
Image backgroundImage(false, true);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,43 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WWP_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms World Party land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, true);
Image collisionImage(false, true);
Image backgroundImage(false, true);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -44,8 +44,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = " Solution Items", " Solution Items", "{8A49F314-1011-4819-A8F6-0EDDA94A9C3D}"
ProjectSection(SolutionItems) = preProject
build.xml = build.xml
test.xml = test.xml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.SchemeEditor", "tool\Syroot.Worms.SchemeEditor\Syroot.Worms.SchemeEditor.csproj", "{4ACE4589-4478-4B4D-8179-87A491B79DAE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -116,6 +119,10 @@ Global
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.Build.0 = Release|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -137,6 +144,7 @@ Global
{392E4CA2-61D9-4BE1-B065-8801A9F102B8} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{212F8090-9775-4098-BD44-9ABC01FBE553} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{1FAB6B9F-2585-46DC-81C0-579DC808C389} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{4ACE4589-4478-4B4D-8179-87A491B79DAE} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}

View File

@ -18,7 +18,8 @@
<!-- Compilation -->
<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>preview</LangVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>

View File

@ -8,7 +8,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public object Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
public object? Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
ByteConverter byteConverter)
{
ExplosionAction explosionAction = instance switch
@ -19,11 +19,12 @@ namespace Syroot.Worms.Armageddon.ProjectX
};
return explosionAction switch
{
ExplosionAction.None => null,
ExplosionAction.Bounce => stream.ReadObject<BounceAction>(),
ExplosionAction.Dig => stream.ReadObject<DigAction>(),
ExplosionAction.Home => stream.ReadObject<HomeAction>(),
ExplosionAction.Roam => stream.ReadObject<RoamAction>(),
_ => null,
_ => throw new NotImplementedException(),
};
}

View File

@ -5,7 +5,7 @@ using System.IO;
using System.Linq;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
@ -177,7 +177,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private object _value;
private object _value = null!;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -198,7 +198,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
/// <summary>
/// Gets or sets the name under which this item is stored.
/// </summary>
public string Key { get; set; }
public string Key { get; set; } = String.Empty;
/// <summary>
/// Gets the type of the item.

View File

@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
@ -44,17 +45,17 @@ namespace Syroot.Worms.Armageddon.ProjectX
public SchemeFlags Flags { get; set; }
public List<Weapon[]> WeaponTables { get; set; }
public IList<Weapon[]> WeaponTables { get; set; } = new List<Weapon[]>();
public Dictionary<string, byte[]> Files { get; set; }
public IDictionary<string, byte[]> Files { get; set; } = new Dictionary<string, byte[]>();
public Dictionary<string, string> Scripts { get; set; }
public IDictionary<string, string> Scripts { get; set; } = new Dictionary<string, string>();
public List<string> Libraries { get; set; }
public IList<string> Libraries { get; set; } = new List<string>();
public string GameSchemeName { get; set; }
public string GameSchemeName { get; set; } = String.Empty;
public Armageddon.Scheme GameScheme { get; set; }
public Armageddon.Scheme? GameScheme { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -75,7 +76,12 @@ namespace Syroot.Worms.Armageddon.ProjectX
int weaponTableCount = reader.ReadInt32();
WeaponTables = new List<Weapon[]>(weaponTableCount);
for (int i = 0; i < weaponTableCount; i++)
WeaponTables.Add(reader.Load<Weapon>(_weaponsPerTable));
{
Weapon[] weaponTable = new Weapon[_weaponsPerTable];
for (int j = 0; j < _weaponsPerTable; j++)
weaponTable[j] = reader.Load<Weapon>();
WeaponTables.Add(weaponTable);
}
// Read a placeholder array.
reader.Seek(sizeof(int));
@ -132,7 +138,8 @@ namespace Syroot.Worms.Armageddon.ProjectX
// Write the weapon tables.
writer.Write(WeaponTables.Count);
foreach (Weapon[] weaponTable in WeaponTables)
writer.Save(weaponTable);
for (int i = 0; i < _weaponsPerTable; i++)
writer.Save(weaponTable[i]);
// Write a placeholder array.
writer.Write(0);

View File

@ -19,7 +19,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
public WeaponAirstrikeSubstyle AirstrikeSubstyle;
[BinaryMember(Converter = typeof(AirstrikeSubstyleConverter))]
public IStyle Style;
public IStyle? Style;
}
public enum WeaponAirstrikeSubstyle : int

View File

@ -20,7 +20,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
WeaponAirstrikeSubstyle.Launcher => stream.ReadObject<LauncherStyle>(),
WeaponAirstrikeSubstyle.Mines => stream.ReadObject<MineStyle>(),
_ => null,
_ => throw new NotImplementedException(),
};
}

View File

@ -47,13 +47,13 @@ namespace Syroot.Worms.Armageddon.ProjectX
public ExplosionAction ExplosionAction { get; set; }
[BinaryMember(Converter = typeof(ActionConverter))]
public IAction Action { get; set; }
public IAction? Action { get; set; }
[BinaryMember(OffsetOrigin = OffsetOrigin.Begin, Offset = 180)]
public ExplosionTarget ExplosionTarget { get; set; }
[BinaryMember(Offset = sizeof(int), Converter = typeof(TargetConverter))]
public ITarget Target { get; set; }
public ITarget? Target { get; set; }
}
public enum ExplosionAction : int

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.Armageddon.ProjectX</AssemblyName>
<Description>.NET library for loading and modifying files of Worms Armageddon ProjectX.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);project x;worms armageddon</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />

View File

@ -50,6 +50,6 @@ namespace Syroot.Worms.Armageddon.ProjectX
public ExplosionAction ExplosionAction { get; set; }
[BinaryMember(Converter = typeof(ActionConverter))]
public IAction Action { get; set; }
public IAction? Action { get; set; }
}
}

View File

@ -8,7 +8,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public object Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
public object? Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
ByteConverter byteConverter)
{
ExplosionTarget explosionTarget = instance switch
@ -18,9 +18,10 @@ namespace Syroot.Worms.Armageddon.ProjectX
};
return explosionTarget switch
{
ExplosionTarget.None => null,
ExplosionTarget.Clusters => stream.ReadObject<ClusterTarget>(),
ExplosionTarget.Fire => stream.ReadObject<FireTarget>(),
_ => null,
_ => throw new NotImplementedException(),
};
}

View File

@ -1,8 +1,9 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
@ -49,7 +50,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
public WeaponThrowAction ThrowAction { get; set; }
public IStyle Style { get; set; }
public IStyle? Style { get; set; }
public bool AmmunitionOverride { get; set; }
@ -59,15 +60,15 @@ namespace Syroot.Worms.Armageddon.ProjectX
public int WeaponSprite { get; set; }
public string NameLong { get; set; }
public string NameLong { get; set; } = String.Empty;
public string Name { get; set; }
public string Name { get; set; } = String.Empty;
public string GridImageFile { get; set; }
public string GridImageFile { get; set; } = String.Empty;
public string GfxDirectoryFile { get; set; }
public string GfxDirectoryFile { get; set; } = String.Empty;
public string[] SpriteNames { get; set; }
public string[] SpriteNames { get; } = new string[5];
public bool DelayOverride { get; set; }
@ -75,27 +76,27 @@ namespace Syroot.Worms.Armageddon.ProjectX
public bool UseLibrary { get; set; }
public string LibraryName { get; set; }
public string LibraryName { get; set; } = String.Empty;
public string LibraryWeaponName { get; set; }
public string LibraryWeaponName { get; set; } = String.Empty;
public string AimSpriteEven { get; set; }
public string AimSpriteEven { get; set; } = String.Empty;
public string AimSpriteUphill { get; set; }
public string AimSpriteUphill { get; set; } = String.Empty;
public string AimSpriteDownhill { get; set; }
public string AimSpriteDownhill { get; set; } = String.Empty;
public string PickSpriteEven { get; set; }
public string PickSpriteEven { get; set; } = String.Empty;
public string PickSpriteUphill { get; set; }
public string PickSpriteUphill { get; set; } = String.Empty;
public string PickSpriteDownhill { get; set; }
public string PickSpriteDownhill { get; set; } = String.Empty;
public string FireSpriteEven { get; set; }
public string FireSpriteEven { get; set; } = String.Empty;
public string FireSpriteUphill { get; set; }
public string FireSpriteUphill { get; set; } = String.Empty;
public string FireSpriteDownhill { get; set; }
public string FireSpriteDownhill { get; set; } = String.Empty;
public bool AimSpriteOverride { get; set; }
@ -244,7 +245,8 @@ namespace Syroot.Worms.Armageddon.ProjectX
Name = reader.ReadString(StringCoding.Int32CharCount);
GridImageFile = reader.ReadString(StringCoding.Int32CharCount);
GfxDirectoryFile = reader.ReadString(StringCoding.Int32CharCount);
SpriteNames = reader.ReadStrings(5, StringCoding.Int32CharCount);
for (int i = 0; i < SpriteNames.Length; i++)
SpriteNames[i] = reader.ReadString(StringCoding.Int32CharCount);
DelayOverride = reader.ReadBoolean(BooleanCoding.Dword);
Delay = reader.ReadInt32();

View File

@ -1,12 +1,13 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents <see cref="MapGeneratorSettings"/> stored in a LEV file.
/// Represents <see cref="MapGenSettings"/> stored in a LEV file.
/// Used by WA and WWP. S. https://worms2d.info/Monochrome_map_(.bit,_.lev).
/// </summary>
public class GeneratedMap : ILoadableFile, ISaveableFile
@ -34,9 +35,9 @@ namespace Syroot.Worms.Armageddon
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="MapGeneratorSettings"/>.
/// Gets or sets the <see cref="MapGenSettings"/>.
/// </summary>
public MapGeneratorSettings Settings { get; set; }
public MapGenSettings Settings { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -44,7 +45,7 @@ namespace Syroot.Worms.Armageddon
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Settings = reader.ReadStruct<MapGeneratorSettings>();
Settings = reader.ReadStruct<MapGenSettings>();
}
/// <inheritdoc/>>
@ -68,4 +69,107 @@ namespace Syroot.Worms.Armageddon
Save(stream);
}
}
/// <summary>
/// Represents the required configuration for the land generator to create a map. This structure appears in LEV
/// files (being their only content), in BIT files (being the header) and in PNG maps stored by WA (being the data
/// of the w2lv or waLV chunks).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MapGenSettings
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>Random seed to generate the land shape (no effect in BIT files).</summary>
public int LandSeed;
/// <summary>Random seed to generate object placements.</summary>
public int ObjectSeed;
/// <summary><see langword="true"/> treats the generated map as a cavern, otherwise as an island, depending on
/// <see cref="Style"/>.</summary>
public bool Cavern;
/// <summary>Style of the land generated, dependending on <see cref="Cavern"/>.</summary>
public MapGenStyle Style;
/// <summary><see langword="true"/> to disable an indestructible border being placed around the map.</summary>
public bool NoIndiBorders;
/// <summary>Probability percentage of map objects to appear on the land, from 0-100.</summary>
public int ObjectPercentage;
/// <summary>Probability percentage of bridges to appear on the land, from 0-100.</summary>
public int BridgePercentage;
/// <summary>Height of the water in percent, from 0-99.</summary>
public int WaterLevel;
/// <summary>Version of the structure, determining the available <see cref="SoilTextureIndex"/> values in Worms
/// Armageddon.</summary>
public MapGenVersion Version;
/// <summary>Texture index determining the style of the generated map.</summary>
public MapGenSoil SoilTextureIndex;
/// <summary>Water color used for the map (deprecated, used only in Worms Armageddon 1.0).</summary>
public int WaterColor;
}
/// <summary>
/// Represents the possible land styles.
/// </summary>
public enum MapGenStyle : int
{
/// <summary>A single island or a cavern.</summary>
OneIslandOrCavern,
/// <summary>Two islands or a double-layer cavern.</summary>
TwoIslandsOrCaverns,
/// <summary>One flat island or a cavern open at the bottom.</summary>
OneFlatIslandOrCavernOpenBottom,
/// <summary>Two flat islands or a cavern open at the left or right.</summary>
TwoFlatIslandsOrCavernOpenLeftRight
}
/// <summary>
/// Represents the possible versions which can be stored in the <see cref="MapGenSettings.Version"/> field.
/// </summary>
public enum MapGenVersion : short
{
/// <summary>Game version 3.0. Last soil texture indices are 26 = Tribal, 27 = Tribal, 28 = Urban. Additionally,
/// floor and ceiling trimming is more severe.</summary>
Version_3_0_0_0 = -1,
/// <summary>Game version 3.5. Last soil texture indices are 26 = Tribal, 27 = Urban, 28 = undefined.</summary>
Version_3_5_0_0 = 0,
/// <summary>Game version 3.6.26.4. Last soil texture indices are 26 = Tools, 27 = Tribal, Urban = 28 (same as
/// in WWP).</summary>
Version_3_6_26_4 = 1
}
/// <summary>
/// Represents the possible soil styles. In case of Worms Armageddon, this list matches
/// <see cref="MapGenVersion.Version_3_6_26_4"/>.
/// </summary>
public enum MapGenSoil : short
{
ClassicBeach,
ClassicDesert,
ClassicFarm,
ClassicForest,
ClassicHell,
Art,
Cheese,
Construction,
Desert,
Dungeon,
Easter,
Forest,
Fruit,
Gulf,
Hell,
Hospital,
Jungle,
Manhattan,
Medieval,
Music,
Pirate,
Snow,
Space,
Sports,
Tentacle,
Time,
Tools,
Tribal,
Urban
}
}

View File

@ -1,20 +1,23 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by WA. S. https://worms2d.info/Land_Data_file.
/// Used by WA and WWP. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
// ---- FIELDS -------------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND", 0x1A
private bool _alignImgData;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -38,6 +41,20 @@ namespace Syroot.Worms.Armageddon
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the format version of the file.
/// </summary>
public LandDataVersion Version { get; set; }
/// <summary>
/// Gets or sets a value indicating whether image data is aligned by 4-bytes. This is the case only for WWP.
/// </summary>
public bool AlignImgData
{
get => _alignImgData;
set => _alignImgData = value;
}
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
@ -54,39 +71,39 @@ namespace Syroot.Worms.Armageddon
public int WaterHeight { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// Gets or sets an unknown value only available in newer W:A versions (apparently since 3.6.28.0).
/// </summary>
public int Unknown { get; set; }
public int? Unknown { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public Point[] ObjectLocations { get; set; }
public IList<Point> ObjectLocations { get; set; } = new List<Point>();
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; }
public Img Foreground { get; set; } = new Img();
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; }
public Img CollisionMask { get; set; } = new Img();
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; }
public Img Background { get; set; } = new Img();
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; }
public string LandTexturePath { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; }
public string WaterDirPath { get; set; } = String.Empty;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -95,24 +112,71 @@ namespace Syroot.Worms.Armageddon
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
int readLocationCount()
{
int oldLocationCount = reader.ReadInt32();
int oldImgStart = oldLocationCount * Unsafe.SizeOf<Point>();
// Check whether the old IMG start would be invalid.
bool isNew = false;
using (reader.TemporarySeek(oldImgStart))
isNew = reader.EndOfStream || reader.ReadUInt32() != Img.Signature;
if (isNew)
{
Unknown = oldLocationCount;
return reader.ReadInt32();
}
else
{
Unknown = null;
return oldLocationCount;
}
}
Img readImage(ref bool aligned)
{
long imgStart = reader.Position;
Img img = new Img(reader.BaseStream, aligned);
// If reading the next image fails, the previous data was aligned and must be reread.
if (!aligned)
{
if (reader.ReadUInt32() == Img.Signature)
{
reader.Position -= sizeof(uint);
}
else
{
reader.Position = imgStart;
img = new Img(reader.BaseStream, true);
aligned = true;
}
}
return img;
}
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
Version = reader.ReadEnum<LandDataVersion>(true);
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
WaterHeight = reader.ReadInt32();
Unknown = reader.ReadInt32();
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
// Read the object locations.
int locationCount = readLocationCount();
ObjectLocations = new List<Point>(locationCount);
for (int i = 0; i < locationCount; i++)
ObjectLocations.Add(reader.ReadStruct<Point>());
// Read the image data.
Foreground = reader.Load<Img>();
CollisionMask = reader.Load<Img>();
Background = reader.Load<Img>();
// Read the image data, which alignment must be detected by trial-and-error.
_alignImgData = Version == LandDataVersion.WormsWorldPartyAqua;
long foreImgLocation = reader.Position;
Foreground = readImage(ref _alignImgData);
CollisionMask = readImage(ref _alignImgData);
Background = new Img(reader.BaseStream, _alignImgData);
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
@ -132,29 +196,31 @@ namespace Syroot.Worms.Armageddon
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
writer.WriteEnum(Version, true);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
writer.Write(WaterHeight);
writer.Write(Unknown);
if (Unknown.HasValue)
writer.Write(Unknown.Value);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length);
writer.WriteStructs(ObjectLocations);
writer.Write(ObjectLocations.Count);
for (int i = 0; i < ObjectLocations.Count; i++)
writer.WriteStruct(ObjectLocations[i]);
// Write the image data.
Foreground.Save(writer.BaseStream);
CollisionMask.Save(writer.BaseStream);
Background.Save(writer.BaseStream);
Foreground.Save(writer.BaseStream, _alignImgData);
CollisionMask.Save(writer.BaseStream, _alignImgData);
Background.Save(writer.BaseStream, _alignImgData);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
}
/// <inheritdoc/>
@ -164,4 +230,17 @@ namespace Syroot.Worms.Armageddon
Save(stream);
}
}
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
/// <summary>
/// Represents the version of <see cref="LandData"/> files, encoded as the signature.
/// </summary>
public enum LandDataVersion : uint
{
/// <summary>Found in W2, WA, WWP, and OW.</summary>
Team17 = 0x1A444E4C, // "LND\x1A"
/// <summary>Found only in WWPA.</summary>
WormsWorldPartyAqua = 0x1B444E4C // "LND\x1B"
}
}

View File

@ -1,158 +0,0 @@
using System.Runtime.InteropServices;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents the required configuration for the land generator to create a map. This structure appears in LEV
/// files (being their only content), in BIT files (being the header) and in PNG maps stored by WA (being the data
/// of the w2lv or waLV chunks).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MapGeneratorSettings
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>
/// The random seed to generate the land shape (no effect in BIT files).
/// </summary>
public int LandSeed;
/// <summary>
/// The random seed to generate object placements.
/// </summary>
public int ObjectSeed;
/// <summary>
/// <c>true</c> treats the generated map as a cavern, otherwise as an island, depending on <see cref="Style"/>.
/// </summary>
[MarshalAs(UnmanagedType.Bool)]
public bool Cavern;
/// <summary>
/// The style of the land generated, dependending on <see cref="Cavern"/>
/// </summary>
public MapGeneratorStyle Style;
/// <summary>
/// <c>true</c> to disable an indestructible border being placed around the map.
/// </summary>
[MarshalAs(UnmanagedType.Bool)]
public bool NoIndestructibleBorders;
/// <summary>
/// The probability percentage of map objects to appear on the land, from 0-100.
/// </summary>
public int ObjectPercentage;
/// <summary>
/// The probability percentage of bridges to appear on the land, from 0-100.
/// </summary>
public int BridgePercentage;
/// <summary>
/// The height of the water in percent, from 0-99.
/// </summary>
public int WaterLevel;
/// <summary>
/// The version of the structure, determining the available <see cref="SoilTextureIndex"/> values in Worms
/// Armageddon.
/// </summary>
public MapGeneratorVersion Version;
/// <summary>
/// Represents the texture index determining the style of the generated map.
/// </summary>
public MapGeneratorSoil SoilTextureIndex;
/// <summary>
/// Represents the water color used for the map (deprecated, used only in Worms Armageddon 1.0).
/// </summary>
public int WaterColor;
}
/// <summary>
/// Represents the possible land styles.
/// </summary>
public enum MapGeneratorStyle : int
{
/// <summary>
/// Represents a single island or a cavern.
/// </summary>
OneIslandOrCavern,
/// <summary>
/// Represents two islands or a double-layer cavern.
/// </summary>
TwoIslandsOrCaverns,
/// <summary>
/// Represents one flat island or a cavern open at the bottom.
/// </summary>
OneFlatIslandOrCavernOpenBottom,
/// <summary>
/// Represents two flat islands or a cavern open at the left or right.
/// </summary>
TwoFlatIslandsOrCavernOpenLeftRight
}
/// <summary>
/// Represents the possible versions which can be stored in the <see cref="MapGeneratorSettings.Version"/> field.
/// </summary>
public enum MapGeneratorVersion : short
{
/// <summary>
/// Game version 3.0. Last soil texture indices are 26 = Tribal, 27 = Tribal, 28 = Urban. Additionally, floor
/// and ceiling trimming is more severe.
/// </summary>
Version_3_0_0_0 = -1,
/// <summary>
/// Game version 3.5. Last soil texture indices are 26 = Tribal, 27 = Urban, 28 = undefined.
/// </summary>
Version_3_5_0_0 = 0,
/// <summary>
/// Game version 3.6.26.4. Last soil texture indices are 26 = Tools, 27 = Tribal, Urban = 28 (same as in WWP).
/// </summary>
Version_3_6_26_4 = 1
}
/// <summary>
/// Represents the possible soil styles. In case of Worms Armageddon, this list matches
/// <see cref="MapGeneratorVersion.Version_3_6_26_4"/>.
/// </summary>
public enum MapGeneratorSoil : short
{
ClassicBeach,
ClassicDesert,
ClassicFarm,
ClassicForest,
ClassicHell,
Art,
Cheese,
Construction,
Desert,
Dungeon,
Easter,
Forest,
Fruit,
Gulf,
Hell,
Hospital,
Jungle,
Manhattan,
Medieval,
Music,
Pirate,
Snow,
Space,
Sports,
Tentacle,
Time,
Tools,
Tribal,
Urban
}
}

View File

@ -145,7 +145,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Represents the stockpiling mode of weapon armory between rounds.
/// </summary>
public enum StockpilingMode : byte
public enum Stockpiling : byte
{
/// <summary>Each round starts with the exact amount of weapons set in the scheme.</summary>
Off,

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
@ -14,30 +15,32 @@ namespace Syroot.Worms.Armageddon
public class WeaponList : IReadOnlyCollection<SchemeWeapon>
{
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
private readonly SchemeWeapon[] _list = new SchemeWeapon[64];
private readonly SchemeWeapon[] _array = new SchemeWeapon[64];
/// <summary>
/// Gets a reference to the weapon with the given <paramref name="index"/>.
/// </summary>
/// <param name="name">The index of the weapon to access.</param>
/// <returns>A reference to the <see cref="SchemeWeapon"/>.</returns>
public ref SchemeWeapon this[int index] => ref _list[index];
public ref SchemeWeapon this[int index] => ref _array[index];
/// <summary>
/// Gets a reference to the weapon with the given <paramref name="name"/>.
/// </summary>
/// <param name="name">The <see cref="Weapon"/> of the weapon to access.</param>
/// <returns>A reference to the <see cref="SchemeWeapon"/>.</returns>
public ref SchemeWeapon this[Weapon name] => ref _list[(int)name];
public ref SchemeWeapon this[Weapon name] => ref _array[(int)name];
/// <inheritdoc/>
public int Count => _list.Length;
public int Count => _array.Length;
/// <inheritdoc/>
public IEnumerator<SchemeWeapon> GetEnumerator() => ((IEnumerable<SchemeWeapon>)_list).GetEnumerator();
public IEnumerator<SchemeWeapon> GetEnumerator() => ((IEnumerable<SchemeWeapon>)_array).GetEnumerator();
internal Span<SchemeWeapon> AsSpan(int length) => _array.AsSpan(0, length);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _array.GetEnumerator();
}
}
}

View File

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
@ -38,8 +38,8 @@ namespace Syroot.Worms.Armageddon
private byte _roundTimeMinutes;
private byte _roundTimeSeconds;
private SchemeExtendedOptions _extended = SchemeExtendedOptions.Default;
private ushort _rwVersionOverride;
private ExtendedOptions _extended = ExtendedOptions.Default;
private ushort _rwVersion;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -116,7 +116,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the delay in seconds between each team's turn to allow relaxed switching of seats.
/// </summary>
public byte HotSeatDelay { get; set; }
public byte HotSeatTime { get; set; }
/// <summary>
/// Gets or sets the time in seconds available for a worm to retreat after using a weapon which ends the turn
@ -139,7 +139,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating whether significant turns will be replayed in offline games.
/// </summary>
public bool AutomaticReplays { get; set; }
public bool Replays { get; set; }
/// <summary>
/// Gets or sets the percentual amount of fall damage applied, relative to normal fall damage being 100%. Valid
@ -155,7 +155,6 @@ namespace Syroot.Worms.Armageddon
{
if (value < 0 || value > 508)
throw new ArgumentOutOfRangeException(nameof(value), "Fall damage must be between 0-508.");
_fallDamage = value >> 2 << 2;
}
}
@ -175,12 +174,12 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating the stockpiling of armory between game rounds.
/// </summary>
public StockpilingMode StockpilingMode { get; set; }
public Stockpiling Stockpiling { get; set; }
/// <summary>
/// Gets or sets a value indicating the worm selection order determining the next worm to be played.
/// </summary>
public WormSelect WormSelectMode { get; set; }
public WormSelect WormSelect { get; set; }
/// <summary>
/// Gets or sets a value indicating the action triggered when Sudden Death starts.
@ -218,7 +217,7 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the percentual probability of a weapon crate to drop between turns. Negative values might crash
/// the game.
/// </summary>
public sbyte WeaponCrateProbability { get; set; }
public sbyte WeaponCrateProb { get; set; }
/// <summary>
/// Gets or sets a value indicating whether donor cards can spawn upon a worm's death.
@ -229,7 +228,7 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the percentual probability of a health crate to drop between turns. Negative values might crash
/// the game.
/// </summary>
public sbyte HealthCrateProbability { get; set; }
public sbyte HealthCrateProb { get; set; }
/// <summary>
/// Gets or sets the amount of health included in a health crate added to the collecting worm's energy.
@ -240,7 +239,7 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the percentual probability of a utility crate to drop between turns. Negative values might
/// crash the game.
/// </summary>
public sbyte UtilityCrateProbability { get; set; }
public sbyte UtilityCrateProb { get; set; }
/// <summary>
/// Gets or sets the type of objects which can be placed on the map.
@ -273,7 +272,7 @@ namespace Syroot.Worms.Armageddon
}
/// <summary>
/// Gets or sets the number of seconds a mine requires to explode. Can be 1-3 and 5-127 seconds.
/// Gets or sets the number of seconds a mine requires to explode. Can be 0-3 and 5-127 seconds.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Value is not between 0-127 or 4.</exception>
public byte MineDelay
@ -289,7 +288,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating whether the mine fuse will be randomly chosen between fractions of 1 to 3
/// seconds. If <c>true</c>, the <see cref="MineDelay"/> setting will be ignored.
/// seconds. If <see langword="true"/>, the <see cref="MineDelay"/> setting will be ignored.
/// </summary>
public bool MineDelayRandom { get; set; }
@ -325,7 +324,7 @@ namespace Syroot.Worms.Armageddon
}
/// <summary>
/// Gets or sets a value indicating whether the turn time is unlimited. If <c>true</c>, the
/// Gets or sets a value indicating whether the turn time is unlimited. If <see langword="true"/>, the
/// <see cref="TurnTime"/> setting will be ignored.
/// </summary>
public bool TurnTimeInfinite { get; set; }
@ -393,40 +392,40 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating whether terrain cannot be destroyed by explosions.
/// </summary>
public bool IndestructibleLand { get; set; }
public bool IndiLand { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Grenade weapon is more powerful.
/// </summary>
public bool UpgradedGrenade { get; set; }
public bool UpgradeGrenade { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Shotgun weapon shoots twice for each of the two shots.
/// </summary>
public bool UpgradedShotgun { get; set; }
public bool UpgradeShotgun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether cluster weapon explode into more clusters.
/// </summary>
public bool UpgradedCluster { get; set; }
public bool UpgradeCluster { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Longbow weapon is more powerful.
/// </summary>
public bool UpgradedLongbow { get; set; }
public bool UpgradeLongbow { get; set; }
// ---- Weapons ----
/// <summary>
/// Gets or sets a value indicating whether team weapons will be given to the teams, overriding the default
/// weapon settings for them.
/// </summary>
public bool EnableTeamWeapons { get; set; }
public bool TeamWeapons { get; set; }
/// <summary>
/// Gets or sets a value indicating whether super weapons can be collected from crates.
/// </summary>
public bool EnableSuperWeapons { get; set; }
// ---- Weapons ----
public bool SuperWeapons { get; set; }
/// <summary>
/// Gets the list of <see cref="SchemeWeapon"/> instances, of which each weapon can be accessed by index or
@ -440,7 +439,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets <see cref="SchemeVersion.Version3"/> or RubberWorm settings.
/// </summary>
public ref SchemeExtendedOptions Extended => ref _extended;
public ref ExtendedOptions Extended => ref _extended;
/// <summary>
/// Gets or sets trailing bytes which could not be parsed and will be attached when saving the scheme anew.
@ -454,12 +453,12 @@ namespace Syroot.Worms.Armageddon
/// </summary>
/// <remarks>Configurable with the /version command in RubberWorm.
/// S. http://worms2d.info/List_of_Worms_Armageddon_logic_versions.</remarks>
public ushort RwVersionOverride
public ushort RwVersion
{
get => _rwVersionOverride;
get => _rwVersion;
set
{
_rwVersionOverride = value;
_rwVersion = value;
// Configure V3 settings according to selected version.
void setBattyRope1To2() => Extended.BattyRope = true;
@ -535,7 +534,7 @@ namespace Syroot.Worms.Armageddon
Extended.WormSelectKeepHotSeat = true;
}
switch (_rwVersionOverride)
switch (_rwVersion)
{
case 11: // 3.5 Beta 3pre15[BattyRope1]
case 12: // 3.5 Beta 3pre15[BattyRope2]
@ -845,30 +844,29 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override bool Equals(object obj) => Equals(obj as Scheme);
public override bool Equals(object? obj) => Equals(obj as Scheme);
/// <inheritdoc/>
public bool Equals(Scheme other)
=> other == this
|| other != null
public bool Equals(Scheme? other)
=> other != null
&& Version == other.Version
&& HotSeatDelay == other.HotSeatDelay
&& HotSeatTime == other.HotSeatTime
&& RetreatTime == other.RetreatTime
&& RetreatTimeRope == other.RetreatTimeRope
&& ShowRoundTime == other.ShowRoundTime
&& AutomaticReplays == other.AutomaticReplays
&& Replays == other.Replays
&& FallDamage == other.FallDamage
&& ArtilleryMode == other.ArtilleryMode
&& SchemeEditor == other.SchemeEditor
&& StockpilingMode == other.StockpilingMode
&& WormSelectMode == other.WormSelectMode
&& Stockpiling == other.Stockpiling
&& WormSelect == other.WormSelect
&& SuddenDeathEvent == other.SuddenDeathEvent
&& WaterRiseRate == other.WaterRiseRate
&& WeaponCrateProbability == other.WeaponCrateProbability
&& WeaponCrateProb == other.WeaponCrateProb
&& DonorCards == other.DonorCards
&& HealthCrateProbability == other.HealthCrateProbability
&& HealthCrateProb == other.HealthCrateProb
&& HealthCrateEnergy == other.HealthCrateEnergy
&& UtilityCrateProbability == other.UtilityCrateProbability
&& UtilityCrateProb == other.UtilityCrateProb
&& ObjectTypes == other.ObjectTypes
&& ObjectCount == other.ObjectCount
&& MineDelay == other.MineDelay
@ -885,40 +883,40 @@ namespace Syroot.Worms.Armageddon
&& 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
&& IndiLand == other.IndiLand
&& UpgradeGrenade == other.UpgradeGrenade
&& UpgradeShotgun == other.UpgradeShotgun
&& UpgradeCluster == other.UpgradeCluster
&& UpgradeLongbow == other.UpgradeLongbow
&& TeamWeapons == other.TeamWeapons
&& SuperWeapons == other.SuperWeapons
&& Weapons.SequenceEqual(other.Weapons)
&& Extended.Equals(ref other.Extended)
&& Extended.Equals(other.Extended)
&& Attachment.SequenceEqual(other.Attachment)
&& RwVersionOverride == other.RwVersionOverride;
&& RwVersion == other.RwVersion;
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Version);
hash.Add(HotSeatDelay);
hash.Add(HotSeatTime);
hash.Add(RetreatTime);
hash.Add(RetreatTimeRope);
hash.Add(ShowRoundTime);
hash.Add(AutomaticReplays);
hash.Add(Replays);
hash.Add(FallDamage);
hash.Add(ArtilleryMode);
hash.Add(SchemeEditor);
hash.Add(StockpilingMode);
hash.Add(WormSelectMode);
hash.Add(Stockpiling);
hash.Add(WormSelect);
hash.Add(SuddenDeathEvent);
hash.Add(WaterRiseRate);
hash.Add(WeaponCrateProbability);
hash.Add(WeaponCrateProb);
hash.Add(DonorCards);
hash.Add(HealthCrateProbability);
hash.Add(HealthCrateProb);
hash.Add(HealthCrateEnergy);
hash.Add(UtilityCrateProbability);
hash.Add(UtilityCrateProb);
hash.Add(ObjectTypes);
hash.Add(ObjectCount);
hash.Add(MineDelay);
@ -935,19 +933,19 @@ namespace Syroot.Worms.Armageddon
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(IndiLand);
hash.Add(UpgradeGrenade);
hash.Add(UpgradeShotgun);
hash.Add(UpgradeCluster);
hash.Add(UpgradeLongbow);
hash.Add(TeamWeapons);
hash.Add(SuperWeapons);
foreach (SchemeWeapon weapon in Weapons)
hash.Add(weapon);
hash.Add(Extended);
foreach (byte attachmentByte in Attachment)
hash.Add(attachmentByte);
hash.Add(RwVersionOverride);
hash.Add(RwVersion);
return hash.ToHashCode();
}
@ -1030,23 +1028,23 @@ namespace Syroot.Worms.Armageddon
Version = reader.ReadEnum<SchemeVersion>(true);
// Read the options.
HotSeatDelay = reader.Read1Byte();
HotSeatTime = reader.Read1Byte();
RetreatTime = reader.Read1Byte();
RetreatTimeRope = reader.Read1Byte();
ShowRoundTime = reader.ReadBoolean();
AutomaticReplays = reader.ReadBoolean();
Replays = reader.ReadBoolean();
FallDamage = reader.ReadByte() * 50 % 256 * 2;
ArtilleryMode = reader.ReadBoolean();
SchemeEditor = reader.ReadEnum<SchemeEditor>(false);
StockpilingMode = reader.ReadEnum<StockpilingMode>(true);
WormSelectMode = reader.ReadEnum<WormSelect>(true);
Stockpiling = reader.ReadEnum<Stockpiling>(true);
WormSelect = reader.ReadEnum<WormSelect>(true);
SuddenDeathEvent = reader.ReadEnum<SuddenDeathEvent>(true);
_waterRiseIndex = reader.Read1Byte();
WeaponCrateProbability = reader.ReadSByte();
WeaponCrateProb = reader.ReadSByte();
DonorCards = reader.ReadBoolean();
HealthCrateProbability = reader.ReadSByte();
HealthCrateProb = reader.ReadSByte();
HealthCrateEnergy = reader.Read1Byte();
UtilityCrateProbability = reader.ReadSByte();
UtilityCrateProb = reader.ReadSByte();
readObjectCombo();
readMineDelay();
DudMines = reader.ReadBoolean();
@ -1059,19 +1057,17 @@ namespace Syroot.Worms.Armageddon
AquaSheep = reader.ReadBoolean();
SheepHeaven = reader.ReadBoolean();
GodWorms = reader.ReadBoolean();
IndestructibleLand = reader.ReadBoolean();
UpgradedGrenade = reader.ReadBoolean();
UpgradedShotgun = reader.ReadBoolean();
UpgradedCluster = reader.ReadBoolean();
UpgradedLongbow = reader.ReadBoolean();
EnableTeamWeapons = reader.ReadBoolean();
EnableSuperWeapons = reader.ReadBoolean();
IndiLand = reader.ReadBoolean();
UpgradeGrenade = reader.ReadBoolean();
UpgradeShotgun = reader.ReadBoolean();
UpgradeCluster = reader.ReadBoolean();
UpgradeLongbow = reader.ReadBoolean();
TeamWeapons = reader.ReadBoolean();
SuperWeapons = reader.ReadBoolean();
// Read the weapons.
Weapons = new WeaponList();
int weaponCount = GetWeaponCount(Version);
for (int i = 0; i < weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeapon>();
reader.ReadStructs(Weapons.AsSpan(GetWeaponCount(Version)));
// Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities.
switch (Version)
@ -1163,23 +1159,23 @@ namespace Syroot.Worms.Armageddon
writer.WriteEnum(version);
// Write the options.
writer.Write(HotSeatDelay);
writer.Write(HotSeatTime);
writer.Write(RetreatTime);
writer.Write(RetreatTimeRope);
writer.Write(ShowRoundTime);
writer.Write(AutomaticReplays);
writer.Write(Replays);
writer.Write((byte)(FallDamage / 4 * 41 % 128));
writer.Write(ArtilleryMode);
writer.WriteEnum(SchemeEditor, false);
writer.WriteEnum(StockpilingMode, true);
writer.WriteEnum(WormSelectMode, true);
writer.WriteEnum(Stockpiling, true);
writer.WriteEnum(WormSelect, true);
writer.WriteEnum(SuddenDeathEvent, true);
writer.Write(_waterRiseIndex);
writer.Write(WeaponCrateProbability);
writer.Write(WeaponCrateProb);
writer.Write(DonorCards);
writer.Write(HealthCrateProbability);
writer.Write(HealthCrateProb);
writer.Write(HealthCrateEnergy);
writer.Write(UtilityCrateProbability);
writer.Write(UtilityCrateProb);
saveObjectTypesAndCount();
saveMineDelayConfig();
writer.Write(DudMines);
@ -1192,22 +1188,20 @@ namespace Syroot.Worms.Armageddon
writer.Write(AquaSheep);
writer.Write(SheepHeaven);
writer.Write(GodWorms);
writer.Write(IndestructibleLand);
writer.Write(UpgradedGrenade);
writer.Write(UpgradedShotgun);
writer.Write(UpgradedCluster);
writer.Write(UpgradedLongbow);
writer.Write(EnableTeamWeapons);
writer.Write(EnableSuperWeapons);
writer.Write(IndiLand);
writer.Write(UpgradeGrenade);
writer.Write(UpgradeShotgun);
writer.Write(UpgradeCluster);
writer.Write(UpgradeLongbow);
writer.Write(TeamWeapons);
writer.Write(SuperWeapons);
// Serialize RubberWorm settings encoded in weapon probabilities.
if (version == SchemeVersion.Version2)
SaveRubberWorm();
// Write the weapons.
int weaponCount = GetWeaponCount(version);
for (int i = 0; i < weaponCount; i++)
writer.WriteStruct(Weapons[i]);
writer.WriteStructs<SchemeWeapon>(Weapons.AsSpan(GetWeaponCount(version)));
// Clear RubberWorm probabilities again or write available extended settings.
switch (version)
@ -1228,10 +1222,10 @@ namespace Syroot.Worms.Armageddon
{
// Reset all super weapon probabilities to remove any settings made by RubberWorm.
for (Weapon superWeapon = Weapon.Freeze; superWeapon <= Weapon.Armageddon; superWeapon++)
Weapons[superWeapon].Crates = 0;
Weapons[superWeapon].Prob = 0;
}
private void ClearExtendedOptions() => Extended = SchemeExtendedOptions.Default;
private void ClearExtendedOptions() => Extended = ExtendedOptions.Default;
private unsafe void LoadExtendedOptions(BinaryStream reader)
{
@ -1242,7 +1236,7 @@ namespace Syroot.Worms.Armageddon
else
{
Span<byte> bytes = new Span<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<SchemeExtendedOptions>());
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0.
byte[] buffer = new byte[(int)Math.Min(bytes.Length, reader.Length - reader.Position)];
@ -1259,7 +1253,7 @@ namespace Syroot.Worms.Armageddon
unchecked
{
// Earthquake flags.
byte prob = (byte)Weapons[Weapon.Earthquake].Crates;
byte prob = (byte)Weapons[Weapon.Earthquake].Prob;
Extended.AutoReaim = prob.GetBit(0);
Extended.CircularAim = prob.GetBit(1);
Extended.AntiLockPower = prob.GetBit(2);
@ -1267,19 +1261,19 @@ namespace Syroot.Worms.Armageddon
Extended.KaosMod = prob.DecodeByte(4, 4);
// Antisink.
Extended.AntiSink = Weapons[Weapon.SheepStrike].Crates != 0;
Extended.AntiSink = Weapons[Weapon.SheepStrike].Prob != 0;
// Crate limit and rate.
prob = (byte)Weapons[Weapon.MagicBullet].Crates;
prob = (byte)Weapons[Weapon.MagicBullet].Prob;
Extended.CrateLimit = prob switch
{
0 => SchemeExtendedOptions.Default.CrateLimit,
0 => ExtendedOptions.Default.CrateLimit,
_ => prob
};
Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Crates;
Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Prob;
// Mole squadron flags.
prob = (byte)Weapons[Weapon.MoleSquadron].Crates;
prob = (byte)Weapons[Weapon.MoleSquadron].Prob;
Extended.ShotDoesntEndTurn = prob.GetBit(0);
Extended.LoseControlDoesntEndTurn = prob.GetBit(1);
Extended.FiringPausesTimer = !prob.GetBit(2);
@ -1290,21 +1284,21 @@ namespace Syroot.Worms.Armageddon
Extended.ExtendedFuse = prob.GetBit(7);
// Flame limit.
prob = (byte)Weapons[Weapon.ScalesOfJustice].Crates;
prob = (byte)Weapons[Weapon.ScalesOfJustice].Prob;
if (prob > 0)
Extended.FlameLimit = (ushort)(prob * 100);
// Friction.
prob = (byte)Weapons[Weapon.SalvationArmy].Crates;
prob = (byte)Weapons[Weapon.SalvationArmy].Prob;
if (prob > 0)
Extended.Friction = prob / 100f;
// Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity.
prob = (byte)Weapons[Weapon.MailStrike].Crates;
prob = (byte)Weapons[Weapon.MailStrike].Prob;
if (prob == 0)
{
Extended.RwGravityType = RwGravityType.None;
Extended.RwGravity = SchemeExtendedOptions.Default.RwGravity;
Extended.RwGravity = ExtendedOptions.Default.RwGravity;
}
else if (prob.GetBit(7) && prob.GetBit(6))
{
@ -1323,7 +1317,7 @@ namespace Syroot.Worms.Armageddon
}
// Rope-knocking force.
prob = (byte)Weapons[Weapon.SuperBananaBomb].Crates;
prob = (byte)Weapons[Weapon.SuperBananaBomb].Prob;
Extended.RopeKnockForce = prob switch
{
0 => null,
@ -1332,35 +1326,35 @@ namespace Syroot.Worms.Armageddon
};
// Maximum rope speed.
prob = (byte)Weapons[Weapon.MineStrike].Crates;
prob = (byte)Weapons[Weapon.MineStrike].Prob;
Extended.RopeMaxSpeed = prob switch
{
0 => SchemeExtendedOptions.Default.RopeMaxSpeed,
0 => ExtendedOptions.Default.RopeMaxSpeed,
Byte.MaxValue => 0,
_ => prob
};
// Select worm any time.
Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Crates).GetBit(0);
Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Prob).GetBit(0);
// Viscosity.
prob = (byte)Weapons[Weapon.ConcreteDonkey].Crates;
prob = (byte)Weapons[Weapon.ConcreteDonkey].Prob;
Extended.Viscosity = prob / 255f;
Extended.ViscosityWorms = (prob & 1) == 1;
// Wind.
prob = (byte)Weapons[Weapon.SuicideBomber].Crates;
Extended.RwWind = (byte)Weapons[Weapon.SuicideBomber].Crates / 255f;
prob = (byte)Weapons[Weapon.SuicideBomber].Prob;
Extended.RwWind = (byte)Weapons[Weapon.SuicideBomber].Prob / 255f;
Extended.RwWindWorms = (prob & 1) == 1;
// Worm bounce.
Extended.WormBounce = (byte)Weapons[Weapon.Armageddon].Crates / 255f;
Extended.WormBounce = (byte)Weapons[Weapon.Armageddon].Prob / 255f;
// Version override.
RwVersionOverride = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[]
RwVersion = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[]
{
(byte)Weapons[Weapon.SelectWorm].Crates,
(byte)Weapons[Weapon.Freeze].Crates
(byte)Weapons[Weapon.SelectWorm].Prob,
(byte)Weapons[Weapon.Freeze].Prob
});
}
@ -1371,7 +1365,7 @@ namespace Syroot.Worms.Armageddon
private unsafe void SaveExtendedOptions(BinaryStream writer)
{
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<SchemeExtendedOptions>());
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0.
writer.Write(bytes.ToArray());
@ -1391,15 +1385,15 @@ namespace Syroot.Worms.Armageddon
prob = prob.SetBit(2, Extended.AntiLockPower);
prob = prob.SetBit(3, Extended.ShotDoesntEndTurnAll);
prob = prob.Encode(Extended.KaosMod, 4, 4);
Weapons[Weapon.Earthquake].Crates = (sbyte)prob;
Weapons[Weapon.Earthquake].Prob = (sbyte)prob;
// Antisink
Weapons[Weapon.SheepStrike].Crates = (sbyte)(Extended.AntiSink ? 1 : 0);
Weapons[Weapon.SheepStrike].Prob = (sbyte)(Extended.AntiSink ? 1 : 0);
// Crate limit and rate.
if (Extended.CrateLimit != SchemeExtendedOptions.Default.CrateLimit)
Weapons[Weapon.MagicBullet].Crates = (sbyte)Math.Min(Byte.MaxValue, Extended.CrateLimit);
Weapons[Weapon.NuclearTest].Crates = (sbyte)Extended.CrateRate;
if (Extended.CrateLimit != ExtendedOptions.Default.CrateLimit)
Weapons[Weapon.MagicBullet].Prob = (sbyte)Math.Min(Byte.MaxValue, Extended.CrateLimit);
Weapons[Weapon.NuclearTest].Prob = (sbyte)Extended.CrateRate;
// Mole squadron flags.
prob = 0;
@ -1411,15 +1405,15 @@ namespace Syroot.Worms.Armageddon
prob = prob.SetBit(5, Extended.ObjectPushByExplosion == true);
prob = prob.SetBit(6, Extended.WeaponsDontChange);
prob = prob.SetBit(7, Extended.ExtendedFuse);
Weapons[Weapon.MoleSquadron].Crates = (sbyte)prob;
Weapons[Weapon.MoleSquadron].Prob = (sbyte)prob;
// Flame limit.
if (Extended.FlameLimit != SchemeExtendedOptions.Default.FlameLimit)
Weapons[Weapon.ScalesOfJustice].Crates = (sbyte)(Extended.FlameLimit / 100);
if (Extended.FlameLimit != ExtendedOptions.Default.FlameLimit)
Weapons[Weapon.ScalesOfJustice].Prob = (sbyte)(Extended.FlameLimit / 100);
// Friction.
if (Extended.Friction != SchemeExtendedOptions.Default.Friction)
Weapons[Weapon.SalvationArmy].Crates = (sbyte)Math.Round(Extended.Friction * 100, 0);
if (Extended.Friction != ExtendedOptions.Default.Friction)
Weapons[Weapon.SalvationArmy].Prob = (sbyte)Math.Round(Extended.Friction * 100, 0);
// Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity.
prob = 0;
@ -1438,10 +1432,10 @@ namespace Syroot.Worms.Armageddon
prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6);
break;
}
Weapons[Weapon.MailStrike].Crates = (sbyte)prob;
Weapons[Weapon.MailStrike].Prob = (sbyte)prob;
// Rope-knocking force.
Weapons[Weapon.SuperBananaBomb].Crates = (sbyte)(Extended.RopeKnockForce switch
Weapons[Weapon.SuperBananaBomb].Prob = (sbyte)(Extended.RopeKnockForce switch
{
null => 0,
0 => Byte.MaxValue,
@ -1449,7 +1443,7 @@ namespace Syroot.Worms.Armageddon
});
// Maximum rope speed.
Weapons[Weapon.MineStrike].Crates = (sbyte)(Extended.RopeMaxSpeed switch
Weapons[Weapon.MineStrike].Prob = (sbyte)(Extended.RopeMaxSpeed switch
{
16/*SchemeExtendedOptions.Default.RopeMaxSpeed*/ => 0,
0 => Byte.MaxValue,
@ -1457,27 +1451,27 @@ namespace Syroot.Worms.Armageddon
});
// Select worm any time.
prob = ((byte)Weapons[Weapon.MBBomb].Crates).SetBit(0, Extended.WormSelectAnytime);
Weapons[Weapon.MBBomb].Crates = (sbyte)prob;
prob = ((byte)Weapons[Weapon.MBBomb].Prob).SetBit(0, Extended.WormSelectAnytime);
Weapons[Weapon.MBBomb].Prob = (sbyte)prob;
// Viscosity.
prob = (byte)Math.Round(Extended.Viscosity * 255, 0);
prob.SetBit(0, Extended.ViscosityWorms);
Weapons[Weapon.ConcreteDonkey].Crates = (sbyte)prob;
Weapons[Weapon.ConcreteDonkey].Prob = (sbyte)prob;
// Wind.
prob = (byte)Math.Round(Extended.RwWind * 255, 0);
prob.SetBit(0, Extended.RwWindWorms);
Weapons[Weapon.SuicideBomber].Crates = (sbyte)prob;
Weapons[Weapon.SuicideBomber].Prob = (sbyte)prob;
// Worm bounce.
Weapons[Weapon.Armageddon].Crates = (sbyte)Math.Round(Extended.WormBounce * 255, 0);
Weapons[Weapon.Armageddon].Prob = (sbyte)Math.Round(Extended.WormBounce * 255, 0);
// Version override.
Span<byte> versionBytes = stackalloc byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersionOverride);
Weapons[Weapon.SelectWorm].Crates = (sbyte)versionBytes[0];
Weapons[Weapon.Freeze].Crates = (sbyte)versionBytes[1];
BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersion);
Weapons[Weapon.SelectWorm].Prob = (sbyte)versionBytes[0];
Weapons[Weapon.Freeze].Prob = (sbyte)versionBytes[1];
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ namespace Syroot.Worms.Armageddon
/// values represent infinity.</summary>
public sbyte Delay;
/// <summary>Probability of this weapon to appear in crates. Has no effect on super weapons.</summary>
public sbyte Crates;
public sbyte Prob;
// ---- OPERATORS ----------------------------------------------------------------------------------------------
@ -39,9 +39,9 @@ namespace Syroot.Worms.Armageddon
=> Ammo == other.Ammo
&& Power == other.Power
&& Delay == other.Delay
&& Crates == other.Crates;
&& Prob == other.Prob;
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(Ammo, Power, Delay, Crates);
public override int GetHashCode() => HashCode.Combine(Ammo, Power, Delay, Prob);
}
}

View File

@ -4,9 +4,9 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms.Armageddon</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats. Simplify Scheme usage.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation. Implement W:A V3 scheme format.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>3.3.0-beta001</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,9 +1,10 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
@ -17,33 +18,17 @@ namespace Syroot.Worms.Armageddon
private const int _missionCount = 33;
private const int _trainingMissionCount = 6;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Team"/> class.
/// </summary>
public Team()
{
WormNames = new string[8];
MissionStatuses = new TeamMissionStatus[_missionCount];
TrainingMissionTimes = new int[_trainingMissionCount];
Unknown1 = new int[10];
TrainingMissionMedals = new byte[_trainingMissionCount];
Unknown2 = new byte[10];
Unknown3 = new int[7];
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name of the team.
/// </summary>
public string Name { get; set; }
public string Name { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the 8 worm names.
/// Gets the 8 worm names.
/// </summary>
public string[] WormNames { get; set; }
public string[] WormNames { get; } = new string[8];
/// <summary>
/// Gets or sets the AI intelligence difficulty level, from 0-5, where 0 is human-controlled.
@ -53,18 +38,18 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the name of soundbank for the voice of team worms.
/// </summary>
public string SoundBankName { get; set; }
public string SoundBankName { get; set; } = String.Empty;
public byte SoundBankLocation { get; set; }
/// <summary>
/// Gets or sets the name of the team fanfare.
/// </summary>
public string FanfareName { get; set; }
public string FanfareName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets a value indicating whether the fanfare with the name stored in <see cref="FanfareName"/>
/// (<c>true</c>) or the player's countries' fanfare should be played (<c>false</c>).
/// (<see langword="true"/>) or the player's countries' fanfare should be played (<see langword="false"/>).
/// </summary>
public byte UseCustomFanfare { get; set; }
@ -76,12 +61,12 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the file name of the team grave bitmap if it uses a custom one.
/// </summary>
public string GraveFileName { get; set; }
public string GraveFileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the team grave bitmap if it uses a custom one.
/// </summary>
public RawBitmap Grave { get; set; }
public RawBitmap Grave { get; set; } = new RawBitmap();
/// <summary>
/// Gets or sets the team's special weapon.
@ -139,19 +124,19 @@ namespace Syroot.Worms.Armageddon
public int DeathmatchDeaths { get; set; }
/// <summary>
/// Gets or sets the array of 33 mission statuses.
/// Gets the array of 33 mission statuses.
/// </summary>
public TeamMissionStatus[] MissionStatuses { get; set; }
public TeamMissionStatus[] MissionStatuses { get; } = new TeamMissionStatus[_missionCount];
/// <summary>
/// Gets or sets the file name of the team flag.
/// </summary>
public string FlagFileName { get; set; }
public string FlagFileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the bitmap of the team flag.
/// </summary>
public RawBitmap Flag { get; set; }
public RawBitmap Flag { get; set; } = new RawBitmap();
/// <summary>
/// Gets or sets the deathmatch rank this team reached.
@ -161,31 +146,19 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the seconds the team required to finish all 6 training missions.
/// </summary>
public int[] TrainingMissionTimes { get; set; }
public int[] TrainingMissionTimes { get; set; } = new int[_trainingMissionCount];
/// <summary>
/// Gets or sets 10 unknown integer values.
/// </summary>
public int[] Unknown1 { get; set; }
public int[] Unknown1 { get; set; } = new int[10];
/// <summary>
/// Gets or sets the medals the team achieved in all 6 training missions.
/// </summary>
public byte[] TrainingMissionMedals { get; set; }
public byte[] TrainingMissionMedals { get; set; } = new byte[_trainingMissionCount];
/// <summary>
/// Gets or sets 10 unknown bytes.
/// </summary>
public byte[] Unknown2 { get; set; }
public byte[] Unknown2 { get; set; } = new byte[10];
/// <summary>
/// Gets or sets 7 unknown integer values.
/// </summary>
public int[] Unknown3 { get; set; }
public int[] Unknown3 { get; set; } = new int[7];
/// <summary>
/// Gets or sets an unknown byte.
/// </summary>
public byte Unknown4 { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -196,7 +169,8 @@ namespace Syroot.Worms.Armageddon
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Name = reader.ReadString(17);
WormNames = reader.ReadStrings(8, 17);
for (int i = 0; i < WormNames.Length; i++)
WormNames[i] = reader.ReadString(17);
CpuLevel = reader.Read1Byte();
SoundBankName = reader.ReadString(0x20);
SoundBankLocation = reader.Read1Byte();
@ -227,7 +201,8 @@ namespace Syroot.Worms.Armageddon
DeathmatchKills = reader.ReadInt32();
Deaths = reader.ReadInt32();
DeathmatchDeaths = reader.ReadInt32();
MissionStatuses = reader.ReadStructs<TeamMissionStatus>(_missionCount);
for (int i = 0; i < _missionCount; i++)
MissionStatuses[i] = reader.ReadStruct<TeamMissionStatus>();
FlagFileName = reader.ReadString(0x20);
Flag = new RawBitmap()
@ -252,19 +227,20 @@ namespace Syroot.Worms.Armageddon
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.WriteString(Name, 17);
writer.WriteStrings(WormNames, 17);
writer.WriteFixedString(Name, 17);
for (int i = 0; i < WormNames.Length; i++)
writer.WriteFixedString(WormNames[i], 17);
writer.Write(CpuLevel);
writer.WriteString(SoundBankName, 0x20);
writer.WriteFixedString(SoundBankName, 0x20);
writer.Write(SoundBankLocation);
writer.WriteString(FanfareName, 0x20);
writer.WriteFixedString(FanfareName, 0x20);
writer.Write(UseCustomFanfare);
writer.Write(GraveSprite);
if (GraveSprite < 0)
{
writer.WriteString(GraveFileName, 0x20);
writer.WriteColors(Grave.Palette);
writer.WriteFixedString(GraveFileName, 0x20);
writer.WriteColors(Grave.Palette!);
writer.Write(Grave.Data);
}
@ -279,10 +255,11 @@ namespace Syroot.Worms.Armageddon
writer.Write(DeathmatchKills);
writer.Write(Deaths);
writer.Write(DeathmatchDeaths);
writer.WriteStructs(MissionStatuses);
for (int i = 0; i < MissionStatuses.Length; i++)
writer.WriteStruct(ref MissionStatuses[i]);
writer.WriteString(FlagFileName, 0x20);
writer.WriteColors(Flag.Palette);
writer.WriteFixedString(FlagFileName, 0x20);
writer.WriteColors(Flag.Palette!);
writer.Write(Flag.Data);
writer.Write(DeathmatchRank);

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
@ -22,7 +22,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() => Teams = new List<Team>();
public TeamContainer() { }
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given
@ -57,7 +57,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public IList<Team> Teams { get; set; }
public IList<Team> Teams { get; set; } = new List<Team>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -77,7 +77,9 @@ namespace Syroot.Worms.Armageddon
Unknown = reader.Read1Byte();
// Read the teams.
Teams = new List<Team>(reader.Load<Team>(teamCount));
Teams = new List<Team>();
while (teamCount-- > 0)
Teams.Add(reader.Load<Team>());
}
/// <inheritdoc/>

View File

@ -3,7 +3,7 @@ using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
@ -32,10 +32,10 @@ namespace Syroot.Worms.Mgame
public int UnknownA { get; set; }
public int UnknownB { get; set; }
public byte[] UnknownC { get; set; }
public byte[] UnknownC { get; set; } = new byte[8];
public Point Center { get; set; }
public Size Size { get; set; }
public IList<IgdImage> Images { get; set; }
public IList<IgdImage> Images { get; set; } = new List<IgdImage>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -82,12 +82,9 @@ namespace Syroot.Worms.Mgame
// Decompress the data.
int dataSize = stream.ReadInt32();
int dataSizeCompressed = stream.ReadInt32();
image.RawBitmap = new RawBitmap
{
Size = new Size(Algebra.NextMultiple(image.Size.Width, 4), image.Size.Height),
Palette = palette,
Data = Decompress(stream, dataSizeCompressed, dataSize)
};
image.RawBitmap.Size = new Size(Algebra.NextMultiple(image.Size.Width, 4), image.Size.Height);
image.RawBitmap.Palette = palette;
image.RawBitmap.Data = Decompress(stream, dataSizeCompressed, dataSize);
Images.Add(image);
}
}

View File

@ -14,6 +14,6 @@ namespace Syroot.Worms.Mgame
public int UnknownC { get; set; }
public Size Size { get; set; }
public Point Center { get; set; }
public RawBitmap RawBitmap { get; set; }
public RawBitmap RawBitmap { get; } = new RawBitmap { BitsPerPixel = 8 };
}
}

View File

@ -23,7 +23,7 @@ namespace Syroot.Worms.Mgame
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(string fileName, Palette palette) => Load(fileName, palette);
public Ksf(string fileName, KsfPalette palette) => Load(fileName, palette);
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the given
@ -31,7 +31,7 @@ namespace Syroot.Worms.Mgame
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(Stream stream, Palette palette) => Load(stream, palette);
public Ksf(Stream stream, KsfPalette palette) => Load(stream, palette);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -48,7 +48,7 @@ namespace Syroot.Worms.Mgame
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(string fileName, Palette palette)
public void Load(string fileName, KsfPalette palette)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream, palette);
@ -60,7 +60,7 @@ namespace Syroot.Worms.Mgame
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(Stream stream, Palette palette)
public void Load(Stream stream, KsfPalette palette)
{
int imageCount = stream.ReadInt32(); // Includes terminator.
_ = stream.ReadInt32(); // data size
@ -91,6 +91,7 @@ namespace Syroot.Worms.Mgame
{
images[i].RawBitmap = new RawBitmap
{
BitsPerPixel = 8,
Size = size,
Palette = palette.Colors,
Data = stream.ReadBytes(dataLength)

View File

@ -10,6 +10,6 @@ namespace Syroot.Worms.Mgame
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public Point Center { get; set; }
public RawBitmap RawBitmap { get; set; }
public RawBitmap? RawBitmap { get; set; } = null;
}
}

View File

@ -1,50 +1,49 @@
using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.Graphics;
using Syroot.Worms.Core.IO;
using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a PAL color palette.
/// Represents a color palette referenced by <see cref="KsfImage"/> data.
/// </summary>
public class Palette : ILoadableFile, IPalette
public class KsfPalette : ILoadableFile, IPalette
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
/// <summary>
/// The number of colors stored in a palette.
/// </summary>
/// <summary>Number of colors stored in a palette.</summary>
internal const int ColorCount = 256;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Palette"/> class.
/// Initializes a new instance of the <see cref="KsfPalette"/> class.
/// </summary>
public Palette() { }
public KsfPalette() { }
/// <summary>
/// Initializes a new instance of the <see cref="Palette"/> class, loading data from the file with the given
/// Initializes a new instance of the <see cref="KsfPalette"/> class, loading data from the file with the given
/// <paramref name="fileName"/>.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Palette(string fileName) => Load(fileName);
public KsfPalette(string fileName) => Load(fileName);
/// <summary>
/// Initializes a new instance of the <see cref="Palette"/> class, loading data from the given
/// Initializes a new instance of the <see cref="KsfPalette"/> class, loading data from the given
/// <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public Palette(Stream stream) => Load(stream);
public KsfPalette(Stream stream) => Load(stream);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the array of 256 colors stored in this palette.
/// </summary>
public Color[] Colors { get; set; } = new Color[ColorCount];
public IList<Color> Colors { get; set; } = new Color[ColorCount];
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------

View File

@ -3,7 +3,7 @@ using System.IO;
using System.IO.MemoryMappedFiles;
using System.Net;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
@ -14,7 +14,7 @@ namespace Syroot.Worms.Mgame
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private string _passwordString;
private string _passwordString = String.Empty;
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -26,17 +26,17 @@ namespace Syroot.Worms.Mgame
/// <summary>
/// Gets or sets an unknown value. Must not exceed 19 characters.
/// </summary>
public string Unknown { get; set; }
public string Unknown { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the address of the game server.
/// </summary>
public IPEndPoint ServerEndPoint { get; set; }
public IPEndPoint ServerEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Gets or sets the initially entered user name. Must not exceed 250 characters.
/// </summary>
public string UserName { get; set; }
public string UserName { get; set; } = String.Empty;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -52,12 +52,12 @@ namespace Syroot.Worms.Mgame
using (BinaryStream stream = new BinaryStream(mappedFile.CreateViewStream(),
encoding: Encodings.Korean, stringCoding: StringCoding.ZeroTerminated))
{
stream.WriteString(Publisher, 16);
stream.WriteString(Unknown, 20);
stream.WriteString(_passwordString, 30);
stream.WriteString($"UID={UserName}", 256);
stream.WriteString(ServerEndPoint.Address.ToString(), 30);
stream.WriteString(ServerEndPoint.Port.ToString(), 914);
stream.WriteFixedString(Publisher, 16);
stream.WriteFixedString(Unknown, 20);
stream.WriteFixedString(_passwordString, 30);
stream.WriteFixedString($"UID={UserName}", 256);
stream.WriteFixedString(ServerEndPoint.Address.ToString(), 30);
stream.WriteFixedString(ServerEndPoint.Port.ToString(), 914);
}
return mappedFile;
}
@ -68,8 +68,8 @@ namespace Syroot.Worms.Mgame
/// </summary>
public string GetPassword()
{
if (_passwordString == null)
return null;
if (String.IsNullOrEmpty(_passwordString))
return String.Empty;
string[] parts = _passwordString.Split(';');
return PasswordCrypto.Decrypt(parts[1], UInt32.Parse(parts[2]));
}
@ -81,8 +81,6 @@ namespace Syroot.Worms.Mgame
/// <param name="password">The password to store encrypted.</param>
/// <param name="key">The key to encrypt with.</param>
public void SetPassword(string password, int key = 1000)
{
_passwordString = $";{PasswordCrypto.Encrypt(password, (uint)key)};{key}";
}
=> _passwordString = $";{PasswordCrypto.Encrypt(password, (uint)key)};{key}";
}
}

View File

@ -2,7 +2,7 @@
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
@ -32,7 +32,7 @@ namespace Syroot.Worms.Mgame
/// <summary>
/// Gets or sets the list of <see cref="LpdElement"/> instances stored by this class.
/// </summary>
public IList<LpdElement> Elements { get; set; }
public IList<LpdElement> Elements { get; set; } = new List<LpdElement>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------

View File

@ -1,4 +1,5 @@
using System.Drawing;
using System;
using System.Drawing;
namespace Syroot.Worms.Mgame
{
@ -10,18 +11,15 @@ namespace Syroot.Worms.Mgame
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a the relative path to the IGD storing the image of this element.
/// Gets or sets a the relative path to the IGD storing the images of this element.
/// </summary>
public string FileName { get; set; }
public string FileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets a the area at which the UI element appears.
/// </summary>
public Rectangle Rectangle { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public int Properties { get; set; }
/// <summary>

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.Mgame</AssemblyName>
<Description>.NET library for loading and modifying files of Mgame Worms clients.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);online worms;worms world party aqua</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,160 +0,0 @@
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.WorldParty
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by WWP. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND", 0x1A
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
public LandData() { }
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given
/// <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public LandData(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
public bool TopBorder { get; set; }
/// <summary>
/// Gets or sets the height of the water in pixels.
/// </summary>
public int WaterHeight { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public Point[] ObjectLocations { get; set; }
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; }
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; }
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; }
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; }
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
WaterHeight = reader.ReadInt32();
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
// Read the image data.
Foreground = new Img(stream, true);
CollisionMask = new Img(stream, true);
Background = new Img(stream, true);
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
writer.Write(WaterHeight);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length);
writer.WriteStructs(ObjectLocations);
// Write the image data.
Foreground.Save(writer.BaseStream, false, true);
CollisionMask.Save(writer.BaseStream, false, true);
Background.Save(writer.BaseStream, false, true);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.WorldParty</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms World Party.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms world party</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,9 +1,10 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.WorldParty
{
@ -17,31 +18,17 @@ namespace Syroot.Worms.WorldParty
private const int _missionCount = 45;
private const int _trainingMissionCount = 35;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Team"/> class.
/// </summary>
public Team()
{
WormNames = new string[8];
MissionStatuses = new TeamMissionStatus[_missionCount];
TrainingMissionTimes = new int[_trainingMissionCount];
WeaponPoints = new byte[46];
Unknown2 = new int[7];
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name of the team.
/// </summary>
public string Name { get; set; }
public string Name { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the 8 worm names.
/// Gets the 8 worm names.
/// </summary>
public string[] WormNames { get; set; }
public string[] WormNames { get; } = new string[8];
/// <summary>
/// Gets or sets the AI intelligence difficulty level, from 0-5, where 0 is human-controlled.
@ -51,18 +38,18 @@ namespace Syroot.Worms.WorldParty
/// <summary>
/// Gets or sets the name of soundbank for the voice of team worms.
/// </summary>
public string SoundBankName { get; set; }
public string SoundBankName { get; set; } = String.Empty;
public byte SoundBankLocation { get; set; }
/// <summary>
/// Gets or sets the name of the team fanfare.
/// </summary>
public string FanfareName { get; set; }
public string FanfareName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets a value indicating whether the fanfare with the name stored in <see cref="FanfareName"/>
/// (<c>true</c>) or the player's countries' fanfare should be played (<c>false</c>).
/// (<see langword="true"/>) or the player's countries' fanfare should be played (<see langword="false"/>).
/// </summary>
public byte UseCustomFanfare { get; set; }
@ -74,12 +61,12 @@ namespace Syroot.Worms.WorldParty
/// <summary>
/// Gets or sets the file name of the team grave bitmap if it uses a custom one.
/// </summary>
public string GraveFileName { get; set; }
public string GraveFileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the team grave bitmap if it uses a custom one.
/// </summary>
public RawBitmap Grave { get; set; }
public RawBitmap Grave { get; set; } = new RawBitmap();
/// <summary>
/// Gets or sets the team's special weapon.
@ -137,23 +124,20 @@ namespace Syroot.Worms.WorldParty
public int DeathmatchDeaths { get; set; }
/// <summary>
/// Gets or sets the array of 33 mission statuses.
/// Gets the array of 33 mission statuses.
/// </summary>
public TeamMissionStatus[] MissionStatuses { get; set; }
public TeamMissionStatus[] MissionStatuses { get; } = new TeamMissionStatus[_missionCount];
/// <summary>
/// Gets or sets the file name of the team flag.
/// </summary>
public string FlagFileName { get; set; }
public string FlagFileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the bitmap of the team flag.
/// </summary>
public RawBitmap Flag { get; set; }
public RawBitmap Flag { get; set; } = new RawBitmap();
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown1 { get; set; }
/// <summary>
@ -164,28 +148,20 @@ namespace Syroot.Worms.WorldParty
/// <summary>
/// Gets or sets the seconds the team required to finish all 35 training missions. The last one is unused.
/// </summary>
public int[] TrainingMissionTimes { get; set; }
/// <summary>
/// Gets or sets a possibly unused training mission time for a 35th mission.
/// </summary>
public int UnknownTrainingMissionTime { get; set; }
public int[] TrainingMissionTimes { get; set; } = new int[_trainingMissionCount];
/// <summary>
/// Gets or sets the 46 weapons which were bought for points. Specific weapons can be accessed with the
/// <see cref="TeamWeaponPoints"/> enumeration.
/// <see cref="TeamPointWeapon"/> enumeration.
/// </summary>
public byte[] WeaponPoints { get; set; }
public byte[] WeaponPoints { get; set; } = new byte[46];
/// <summary>
/// Gets or sets the fort of the team.
/// </summary>
public byte Fort { get; set; }
/// <summary>
/// Gets or sets 7 unknown integer values.
/// </summary>
public int[] Unknown2 { get; set; }
public int[] Unknown2 { get; set; } = new int[7];
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -194,18 +170,19 @@ namespace Syroot.Worms.WorldParty
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Name = reader.ReadString(17);
WormNames = reader.ReadStrings(8, 17);
Name = reader.ReadFixedString(17);
for (int i = 0; i < WormNames.Length; i++)
WormNames[i] = reader.ReadFixedString(17);
CpuLevel = reader.Read1Byte();
SoundBankName = reader.ReadString(0x20);
SoundBankName = reader.ReadFixedString(0x20);
SoundBankLocation = reader.Read1Byte();
FanfareName = reader.ReadString(0x20);
FanfareName = reader.ReadFixedString(0x20);
UseCustomFanfare = reader.Read1Byte();
GraveSprite = reader.ReadSByte();
if (GraveSprite < 0)
{
GraveFileName = reader.ReadString(0x20);
GraveFileName = reader.ReadFixedString(0x20);
Grave = new RawBitmap()
{
BitsPerPixel = 8,
@ -226,9 +203,10 @@ namespace Syroot.Worms.WorldParty
DeathmatchKills = reader.ReadInt32();
Deaths = reader.ReadInt32();
DeathmatchDeaths = reader.ReadInt32();
MissionStatuses = reader.ReadStructs<TeamMissionStatus>(_missionCount);
for (int i = 0; i < _missionCount; i++)
MissionStatuses[i] = reader.ReadStruct<TeamMissionStatus>();
FlagFileName = reader.ReadString(0x20);
FlagFileName = reader.ReadFixedString(0x20);
Flag = new RawBitmap()
{
BitsPerPixel = 8,
@ -250,19 +228,20 @@ namespace Syroot.Worms.WorldParty
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.WriteString(Name, 17);
writer.WriteStrings(WormNames, 17);
writer.WriteFixedString(Name, 17);
for (int i = 0; i < 8; i++)
writer.WriteFixedString(WormNames[i], 17);
writer.Write(CpuLevel);
writer.WriteString(SoundBankName, 0x20);
writer.WriteFixedString(SoundBankName, 0x20);
writer.Write(SoundBankLocation);
writer.WriteString(FanfareName, 0x20);
writer.WriteFixedString(FanfareName, 0x20);
writer.Write(UseCustomFanfare);
writer.Write(GraveSprite);
if (GraveSprite < 0)
{
writer.WriteString(GraveFileName, 0x20);
writer.WriteColors(Grave.Palette);
writer.WriteFixedString(GraveFileName, 0x20);
writer.WriteColors(Grave.Palette!);
writer.Write(Grave.Data);
}
@ -277,16 +256,16 @@ namespace Syroot.Worms.WorldParty
writer.Write(DeathmatchKills);
writer.Write(Deaths);
writer.Write(DeathmatchDeaths);
writer.WriteStructs(MissionStatuses);
for (int i = 0; i < MissionStatuses.Length; i++)
writer.WriteStruct(MissionStatuses[i]);
writer.WriteString(FlagFileName, 0x20);
writer.WriteColors(Flag.Palette);
writer.WriteFixedString(FlagFileName, 0x20);
writer.WriteColors(Flag.Palette!);
writer.Write(Flag.Data);
writer.Write(Unknown1);
writer.Write(DeathmatchRank);
writer.Write(TrainingMissionTimes);
writer.Write(UnknownTrainingMissionTime);
writer.Write(WeaponPoints);
writer.Write(Fort);
writer.Write(Unknown2);
@ -299,14 +278,11 @@ namespace Syroot.Worms.WorldParty
[DebuggerDisplay("TeamMissionStatus Attemps={Attempts} Medal={Medal}")]
public struct TeamMissionStatus
{
/// <summary>
/// The number of attempts the team required to solve the mission.
/// </summary>
public int Attempts;
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>
/// The medal the team got to solve the mission.
/// </summary>
/// <summary>Number of attempts the team required to solve the mission.</summary>
public int Attempts;
/// <summary>Medal the team got to solve the mission.</summary>
public int Medal;
}
@ -315,44 +291,21 @@ namespace Syroot.Worms.WorldParty
/// </summary>
public enum TeamWeapon : byte
{
/// <summary>
/// The Flame Thrower weapon.
/// </summary>
/// <summary>The Flame Thrower weapon.</summary>
Flamethrower,
/// <summary>
/// The Mole Bomb weapon.
/// </summary>
/// <summary>The Mole Bomb weapon.</summary>
MoleBomb,
/// <summary>
/// The Old Woman weapon.
/// </summary>
/// <summary>The Old Woman weapon.</summary>
OldWoman,
/// <summary>
/// The Homing Pigeon weapon.
/// </summary>
/// <summary>The Homing Pigeon weapon.</summary>
HomingPigeon,
/// <summary>
/// The Sheep Launcher weapon.
/// </summary>
/// <summary>The Sheep Launcher weapon.</summary>
SheepLauncher,
/// <summary>
/// The Mad Cow weapon.
/// </summary>
/// <summary>The Mad Cow weapon.</summary>
MadCow,
/// <summary>
/// The Holy Hand Grenade weapon.
/// </summary>
/// <summary>The Holy Hand Grenade weapon.</summary>
HolyHandGrenade,
/// <summary>
/// The Super Sheep or Aqua Sheep weapon.
/// </summary>
/// <summary>The Super Sheep or Aqua Sheep weapon.</summary>
SuperSheep
}
@ -360,236 +313,99 @@ namespace Syroot.Worms.WorldParty
/// Represents the weapons and utilities being an index into the <see cref="Team.WeaponPoints"/> array to store the
/// amount the team bought.
/// </summary>
public enum TeamWeaponPoints
public enum TeamPointWeapon
{
/// <summary>
/// The Bazooka weapon.
/// </summary>
/// <summary>The Bazooka weapon.</summary>
Bazooka,
/// <summary>
/// The Homing Missile weapon.
/// </summary>
/// <summary>The Homing Missile weapon.</summary>
HomingMissile,
/// <summary>
/// The Mortar weapon.
/// </summary>
/// <summary>The Mortar weapon.</summary>
Mortar,
/// <summary>
/// The Grenade weapon.
/// </summary>
/// <summary>The Grenade weapon.</summary>
Grenade,
/// <summary>
/// The Cluster Bomb weapon.
/// </summary>
/// <summary>The Cluster Bomb weapon.</summary>
ClusterBomb,
/// <summary>
/// The Skunk weapon.
/// </summary>
/// <summary>The Skunk weapon.</summary>
Skunk,
/// <summary>
/// The Petrol Bomb weapon.
/// </summary>
/// <summary>The Petrol Bomb weapon.</summary>
PetrolBomb,
/// <summary>
/// The Banana Bomb weapon.
/// </summary>
/// <summary>The Banana Bomb weapon.</summary>
BananaBomb,
/// <summary>
/// The Handgun weapon.
/// </summary>
/// <summary>The Handgun weapon.</summary>
Handgun,
/// <summary>
/// The Shotgun weapon.
/// </summary>
/// <summary>The Shotgun weapon.</summary>
Shotgun,
/// <summary>
/// The Uzi weapon.
/// </summary>
/// <summary>The Uzi weapon.</summary>
Uzi,
/// <summary>
/// The Minigun weapon.
/// </summary>
/// <summary>The Minigun weapon.</summary>
Minigun,
/// <summary>
/// The Longbow weapon.
/// </summary>
/// <summary>The Longbow weapon.</summary>
Longbow,
/// <summary>
/// The Airstrike weapon.
/// </summary>
/// <summary>The Airstrike weapon.</summary>
Airstrike,
/// <summary>
/// The Napalm Strike weapon.
/// </summary>
/// <summary>The Napalm Strike weapon.</summary>
NapalmStrike,
/// <summary>
/// The Mine weapon.
/// </summary>
/// <summary>The Mine weapon.</summary>
Mine,
/// <summary>
/// The Firepunch weapon.
/// </summary>
/// <summary>The Firepunch weapon.</summary>
Firepunch,
/// <summary>
/// The Dragonball weapon.
/// </summary>
/// <summary>The Dragonball weapon.</summary>
Dragonball,
/// <summary>
/// The Kamikaze weapon.
/// </summary>
/// <summary>The Kamikaze weapon.</summary>
Kamikaze,
/// <summary>
/// The Prod weapon.
/// </summary>
/// <summary>The Prod weapon.</summary>
Prod,
/// <summary>
/// The Battle Axe weapon.
/// </summary>
/// <summary>The Battle Axe weapon.</summary>
BattleAxe,
/// <summary>
/// The Blowtorch weapon.
/// </summary>
/// <summary>The Blowtorch weapon.</summary>
Blowtorch,
/// <summary>
/// The Pneumatic Drill weapon.
/// </summary>
/// <summary>The Pneumatic Drill weapon.</summary>
PneumaticDrill,
/// <summary>
/// The Girder weapon.
/// </summary>
/// <summary>The Girder weapon.</summary>
Girder,
/// <summary>
/// The Ninja Rope weapon.
/// </summary>
/// <summary>The Ninja Rope weapon.</summary>
NinjaRope,
/// <summary>
/// The Parachute weapon.
/// </summary>
/// <summary>The Parachute weapon.</summary>
Parachute,
/// <summary>
/// The Bungee weapon.
/// </summary>
/// <summary>The Bungee weapon.</summary>
Bungee,
/// <summary>
/// The Teleport weapon.
/// </summary>
/// <summary>The Teleport weapon.</summary>
Teleport,
/// <summary>
/// The Dynamite weapon.
/// </summary>
/// <summary>The Dynamite weapon.</summary>
Dynamite,
/// <summary>
/// The Sheep weapon.
/// </summary>
/// <summary>The Sheep weapon.</summary>
Sheep,
/// <summary>
/// The Baseball Bat weapon.
/// </summary>
/// <summary>The Baseball Bat weapon.</summary>
BaseballBat,
/// <summary>
/// The Flame Thrower weapon.
/// </summary>
/// <summary>The Flame Thrower weapon.</summary>
Flamethrower,
/// <summary>
/// The Homing Pigeon weapon.
/// </summary>
/// <summary>The Homing Pigeon weapon.</summary>
HomingPigeon,
/// <summary>
/// The Mad Cow weapon.
/// </summary>
/// <summary>The Mad Cow weapon.</summary>
MadCow,
/// <summary>
/// The Holy Hand Grenade weapon.
/// </summary>
/// <summary>The Holy Hand Grenade weapon.</summary>
HolyHandGrenade,
/// <summary>
/// The Old Woman weapon.
/// </summary>
/// <summary>The Old Woman weapon.</summary>
OldWoman,
/// <summary>
/// The Sheep Launcher weapon.
/// </summary>
/// <summary>The Sheep Launcher weapon.</summary>
SheepLauncher,
/// <summary>
/// The Super Sheep or Aqua Sheep weapon.
/// </summary>
/// <summary>The Super Sheep or Aqua Sheep weapon.</summary>
SuperSheep,
/// <summary>
/// The Mole Bomb weapon.
/// </summary>
/// <summary>The Mole Bomb weapon.</summary>
MoleBomb,
/// <summary>
/// The Jetpack utility.
/// </summary>
/// <summary>The Jetpack utility.</summary>
Jetpack,
/// <summary>
/// The Low Gravity utility.
/// </summary>
/// <summary>The Low Gravity utility.</summary>
LowGravity,
/// <summary>
/// The Laser Sight utility.
/// </summary>
/// <summary>The Laser Sight utility.</summary>
LaserSight,
/// <summary>
/// The Fast Walk utility.
/// </summary>
/// <summary>The Fast Walk utility.</summary>
FastWalk,
/// <summary>
/// The Invisibility utility.
/// </summary>
/// <summary>The Invisibility utility.</summary>
Invisibility,
/// <summary>
/// The Suicide Bomber weapon.
/// </summary>
/// <summary>The Suicide Bomber weapon.</summary>
SuicideBomber,
/// <summary>
/// The Worm Select utility.
/// </summary>
/// <summary>The Worm Select utility.</summary>
WormSelect
}
}

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.WorldParty
{
@ -14,14 +14,14 @@ namespace Syroot.Worms.WorldParty
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "WWP"; // 0-terminated.
private const uint _signature = 0x00505757; // "WWP\0"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() => Teams = new List<Team>();
public TeamContainer() { }
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given
@ -43,25 +43,19 @@ namespace Syroot.Worms.WorldParty
/// </summary>
public byte Version { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown1 { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown2 { get; set; }
/// <summary>
/// Gets or sets 840 unknown bytes, all possibly 0.
/// </summary>
public byte[] Unknown3 { get; set; }
public byte[] Unknown3 { get; set; } = new byte[840];
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public List<Team> Teams { get; set; }
public IList<Team> Teams { get; set; } = new List<Team>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -71,7 +65,7 @@ namespace Syroot.Worms.WorldParty
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
if (reader.ReadUInt32() != _signature)
throw new InvalidDataException("Invalid WWP file signature.");
Version = reader.Read1Byte(); // Really version?
@ -82,7 +76,9 @@ namespace Syroot.Worms.WorldParty
Unknown3 = reader.ReadBytes(840);
// Read the teams.
Teams = new List<Team>(reader.Load<Team>(teamCount));
Teams = new List<Team>(teamCount);
while (teamCount-- > 0)
Teams.Add(reader.Load<Team>());
}
/// <inheritdoc/>
@ -98,7 +94,7 @@ namespace Syroot.Worms.WorldParty
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature, StringCoding.ZeroTerminated);
writer.Write(_signature);
writer.Write(Version);
// Write global settings.

View File

@ -1,20 +1,22 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by W2. S. https://worms2d.info/Land_Data_file.
/// Used by W2 and OW. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND", 0x1A
private const int _signature = 0x1A444E4C; // "LND\x1A"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -51,7 +53,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public Point[] ObjectLocations { get; set; }
public IList<Point> ObjectLocations { get; set; } = new List<Point>();
/// <summary>
/// Gets or sets an unknown value, seeming to be 0 most of the time.
@ -61,32 +63,32 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; }
public Img Foreground { get; set; } = new Img();
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; }
public Img CollisionMask { get; set; } = new Img();
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; }
public Img Background { get; set; } = new Img();
/// <summary>
/// Gets or sets an image of unknown use.
/// </summary>
public Img UnknownImage { get; set; }
public Img UnknownImage { get; set; } = new Img();
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; }
public string LandTexturePath { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; }
public string WaterDirPath { get; set; } = String.Empty;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -105,7 +107,10 @@ namespace Syroot.Worms.Worms2
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
ObjectLocations = new List<Point>();
int locationCount = reader.ReadInt32();
for (int i = 0; i < locationCount; i++)
ObjectLocations.Add(reader.ReadStruct<Point>());
Unknown = reader.ReadInt32();
// Read the image data.
@ -140,8 +145,9 @@ namespace Syroot.Worms.Worms2
writer.Write(TopBorder, BooleanCoding.Dword);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length);
writer.WriteStructs(ObjectLocations);
writer.Write(ObjectLocations.Count);
for (int i = 0; i < ObjectLocations.Count; i++)
writer.WriteStruct(ObjectLocations[i]);
writer.Write(Unknown);
// Write the image data.
@ -154,7 +160,7 @@ namespace Syroot.Worms.Worms2
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
}
/// <inheritdoc/>

View File

@ -3,222 +3,98 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Represents the method to determine the next turn's worm.
/// </summary>
public enum SchemeWormSelect : int
public enum WormSelect : int
{
/// <summary>
/// Worms are selected in the order in which they appear in the team.
/// </summary>
Sequential = 0,
/// <summary>
/// Worms are selected randomly.
/// </summary>
Random = 1,
/// <summary>
/// Worms are selected by a computed rating system.
/// </summary>
Intelligent = 2,
/// <summary>
/// Worms are selected by the player.
/// </summary>
Manual = 3
/// <summary>Worms are selected in the order in which they appear in the team.</summary>
Sequential,
/// <summary>Worms are selected randomly.</summary>
Random,
/// <summary>Worms are selected by a computed rating system.</summary>
Intelligent,
/// <summary>Worms are selected by the player.</summary>
Manual
}
/// <summary>
/// Represents the weapons in the game.
/// </summary>
public enum SchemeWeapon
public enum Weapon
{
/// <summary>
/// The Bazooka weapon.
/// </summary>
/// <summary>The Bazooka weapon.</summary>
Bazooka,
/// <summary>
/// The Homing Missile weapon.
/// </summary>
/// <summary>The Homing Missile weapon.</summary>
HomingMissile,
/// <summary>
/// The Grenade weapon.
/// </summary>
/// <summary>The Grenade weapon.</summary>
Grenade,
/// <summary>
/// The Cluster Bomb weapon.
/// </summary>
/// <summary>The Cluster Bomb weapon.</summary>
ClusterBomb,
/// <summary>
/// The Banana Bomb weapon.
/// </summary>
/// <summary>The Banana Bomb weapon.</summary>
BananaBomb,
/// <summary>
/// The Holy Hand Grenade weapon.
/// </summary>
/// <summary>The Holy Hand Grenade weapon.</summary>
HolyHandGrenade,
/// <summary>
/// The Homing Cluster Bomb weapon.
/// </summary>
/// <summary>The Homing Cluster Bomb weapon.</summary>
HomingClusterBomb,
/// <summary>
/// The Petrol Bomb weapon.
/// </summary>
/// <summary>The Petrol Bomb weapon.</summary>
PetrolBomb,
/// <summary>
/// The Shotgun weapon.
/// </summary>
/// <summary>The Shotgun weapon.</summary>
Shotgun,
/// <summary>
/// The Handgun weapon.
/// </summary>
/// <summary>The Handgun weapon.</summary>
Handgun,
/// <summary>
/// The Uzi weapon.
/// </summary>
/// <summary>The Uzi weapon.</summary>
Uzi,
/// <summary>
/// The Minigun weapon.
/// </summary>
/// <summary>The Minigun weapon.</summary>
Minigun,
/// <summary>
/// The Firepunch weapon.
/// </summary>
/// <summary>The Firepunch weapon.</summary>
Firepunch,
/// <summary>
/// The Dragonball weapon.
/// </summary>
/// <summary>The Dragonball weapon.</summary>
Dragonball,
/// <summary>
/// The Kamikaze weapon.
/// </summary>
/// <summary>The Kamikaze weapon.</summary>
Kamikaze,
/// <summary>
/// The Dynamite weapon.
/// </summary>
/// <summary>The Dynamite weapon.</summary>
Dynamite,
/// <summary>
/// The Mine weapon.
/// </summary>
/// <summary>The Mine weapon.</summary>
Mine,
/// <summary>
/// The Ming Vase weapon.
/// </summary>
/// <summary>The Ming Vase weapon.</summary>
MingVase,
/// <summary>
/// The Airstrike weapon.
/// </summary>
/// <summary>The Airstrike weapon.</summary>
Airstrike,
/// <summary>
/// The Homing Airstrike weapon.
/// </summary>
/// <summary>The Homing Airstrike weapon.</summary>
HomingAirstrike,
/// <summary>
/// The Napalm Strike weapon.
/// </summary>
/// <summary>The Napalm Strike weapon.</summary>
NapalmStrike,
/// <summary>
/// The Mail Strike weapon.
/// </summary>
/// <summary>The Mail Strike weapon.</summary>
MailStrike,
/// <summary>
/// The Girder weapon.
/// </summary>
/// <summary>The Girder weapon.</summary>
Girder,
/// <summary>
/// The Pneumatic Drill weapon.
/// </summary>
/// <summary>The Pneumatic Drill weapon.</summary>
PneumaticDrill,
/// <summary>
/// The Baseball Bat weapon.
/// </summary>
/// <summary>The Baseball Bat weapon.</summary>
BaseballBat,
/// <summary>
/// The Prod weapon.
/// </summary>
/// <summary>The Prod weapon.</summary>
Prod,
/// <summary>
/// The Teleport weapon.
/// </summary>
/// <summary>The Teleport weapon.</summary>
Teleport,
/// <summary>
/// The Ninja Rope weapon.
/// </summary>
/// <summary>The Ninja Rope weapon.</summary>
NinjaRope,
/// <summary>
/// The Bungee weapon.
/// </summary>
/// <summary>The Bungee weapon.</summary>
Bungee,
/// <summary>
/// The Parachute weapon.
/// </summary>
/// <summary>The Parachute weapon.</summary>
Parachute,
/// <summary>
/// The Sheep weapon.
/// </summary>
/// <summary>The Sheep weapon.</summary>
Sheep,
/// <summary>
/// The Mad Cow weapon.
/// </summary>
/// <summary>The Mad Cow weapon.</summary>
MadCow,
/// <summary>
/// The Old Woman weapon.
/// </summary>
/// <summary>The Old Woman weapon.</summary>
OldWoman,
/// <summary>
/// The Mortar weapon.
/// </summary>
/// <summary>The Mortar weapon.</summary>
Mortar,
/// <summary>
/// The Blowtorch weapon.
/// </summary>
/// <summary>The Blowtorch weapon.</summary>
Blowtorch,
/// <summary>
/// The Homing Pigeon weapon.
/// </summary>
/// <summary>The Homing Pigeon weapon.</summary>
HomingPigeon,
/// <summary>
/// The Super Sheep weapon.
/// </summary>
/// <summary>The Super Sheep weapon.</summary>
SuperSheep,
/// <summary>
/// The Super Banana Bomb weapon.
/// </summary>
/// <summary>The Super Banana Bomb weapon.</summary>
SuperBananaBomb
}
}

View File

@ -1,7 +1,7 @@
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
@ -140,7 +140,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets a value indicating the worm selection order determining the next worm to be played.
/// </summary>
public SchemeWormSelect WormSelectMode { get; set; }
public WormSelect WormSelectMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the chat box will be closed upon starting to move a worm or stays
@ -236,7 +236,7 @@ namespace Syroot.Worms.Worms2
SuddenDeathHealthDrop = reader.ReadBoolean(BooleanCoding.Dword);
IndestructibleBorder = reader.ReadBoolean(BooleanCoding.Dword);
RestrictGirders = reader.ReadBoolean(BooleanCoding.Dword);
WormSelectMode = reader.ReadEnum<SchemeWormSelect>(true);
WormSelectMode = reader.ReadEnum<WormSelect>(true);
ExtendedChatControls = reader.ReadBoolean(BooleanCoding.Dword);
HotSeatDelay = reader.ReadInt32();
EnableStockpiling = reader.ReadBoolean(BooleanCoding.Dword);

View File

@ -0,0 +1,171 @@
using System;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents the configuration of a weapon.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SchemeWeapon : IEquatable<SchemeWeapon>
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>Amount of this weapon with which a team is equipped at game start. 10 and negative values represent
/// infinity.</summary>
public int Ammo;
/// <summary>Number of turns required to be taken by each team before this weapon becomes available.</summary>
public int Delay;
/// <summary>Retreat time after using this weapon. 0 uses the setting from the game options.</summary>
public int RetreatTime;
/// <summary><see langword="true"/> to preselect this weapon in the next turn; otherwise <see langword="false"/>.</summary>
public bool Remember;
public int Unused1;
/// <summary>Amount of this weapon added to the team armory when collected from a crate.</summary>
public int CrateAmmo;
/// <summary>Amount of bullets shot at once.</summary>
public int BulletCount;
/// <summary>Percentual chance of this weapon to appear in crates.</summary>
public int Probability;
/// <summary>Damage measured in health points which also determines the blast radius.</summary>
public int Damage;
/// <summary>Pushing power measured in percent.</summary>
public int ExplosionPower;
/// <summary>Offset to the bottom of an explosion, measured in percent.</summary>
public int ExplosionBias;
/// <summary>Milliseconds required before this weapon starts flying towards its target.</summary>
public int HomingDelay;
/// <summary>Length in milliseconds this weapon flies towards its target before giving up.</summary>
public int HomingTime;
/// <summary>Percentual amount this weaopn is affected by wind.</summary>
public int WindResponse;
public int Unused2;
/// <summary>Number of clusters into which this weapon explodes.</summary>
public int ClusterCount;
/// <summary>Speed in which clusters are dispersed in percent.</summary>
public int ClusterLaunchPower;
/// <summary>Angle in which clusters are dispersed in degrees.</summary>
public int ClusterLaunchAngle;
/// <summary>Damage of clusters measured in health points which also determines the blast radius.</summary>
public int ClusterDamage;
/// <summary>Overrides the fuse of this weapon, 0 for default.</summary>
public int Fuse;
/// <summary>Amount of fire created.</summary>
public int FireAmount;
/// <summary>Speed in which fire spreads, measured in percent.</summary>
public int FireSpreadSpeed;
/// <summary>Period in which fire burns, measured in percent.</summary>
public int FireTime;
/// <summary>Melee impact force in percent.</summary>
public int MeleeForce;
/// <summary>Melee impact angle in degrees.</summary>
public int MeleeAngle;
/// <summary>Melee damage in health points.</summary>
public int MeleeDamage;
/// <summary>Height of the fire punch jump, measured in percent.</summary>
public int FirepunchHeight;
/// <summary>Damage a dragon ball causes, measured in health points.</summary>
public int DragonballDamage;
/// <summary>Power in which a dragon ball launches hit worms, measured in percent.</summary>
public int DragonballPower;
/// <summary>Angle in which a dragon ball launches hit worms, measured in degrees.</summary>
public int DragonballAngle;
/// <summary>Lifetime of a launched dragon ball measured in milliseconds.</summary>
public int DragonballTime;
/// <summary>Length of digging measured in milliseconds. Applies to Kamikaze and digging tools.</summary>
public int DiggingTime;
/// <summary>Amount of airstrike clusters thrown.</summary>
public int StrikeClusterCount;
/// <summary>Angle in which bullets are dispersed, measured in degrees.</summary>
public int BulletSpreadAngle;
// ---- 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)
=> Ammo == other.Ammo
&& Delay == other.Delay
&& RetreatTime == other.RetreatTime
&& Remember == other.Remember
&& Unused1 == other.Unused1
&& CrateAmmo == other.CrateAmmo
&& BulletCount == other.BulletCount
&& Probability == other.Probability
&& Damage == other.Damage
&& ExplosionPower == other.ExplosionPower
&& ExplosionBias == other.ExplosionBias
&& HomingDelay == other.HomingDelay
&& HomingTime == other.HomingTime
&& WindResponse == other.WindResponse
&& Unused2 == other.Unused2
&& ClusterCount == other.ClusterCount
&& ClusterLaunchPower == other.ClusterLaunchPower
&& ClusterLaunchAngle == other.ClusterLaunchAngle
&& ClusterDamage == other.ClusterDamage
&& Fuse == other.Fuse
&& FireAmount == other.FireAmount
&& FireSpreadSpeed == other.FireSpreadSpeed
&& FireTime == other.FireTime
&& MeleeForce == other.MeleeForce
&& MeleeAngle == other.MeleeAngle
&& MeleeDamage == other.MeleeDamage
&& FirepunchHeight == other.FirepunchHeight
&& DragonballDamage == other.DragonballDamage
&& DragonballPower == other.DragonballPower
&& DragonballAngle == other.DragonballAngle
&& DragonballTime == other.DragonballTime
&& DiggingTime == other.DiggingTime
&& StrikeClusterCount == other.StrikeClusterCount
&& BulletSpreadAngle == other.BulletSpreadAngle;
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Ammo);
hash.Add(Delay);
hash.Add(RetreatTime);
hash.Add(Remember);
hash.Add(Unused1);
hash.Add(CrateAmmo);
hash.Add(BulletCount);
hash.Add(Probability);
hash.Add(Damage);
hash.Add(ExplosionPower);
hash.Add(ExplosionBias);
hash.Add(HomingDelay);
hash.Add(HomingTime);
hash.Add(WindResponse);
hash.Add(Unused2);
hash.Add(ClusterCount);
hash.Add(ClusterLaunchPower);
hash.Add(ClusterLaunchAngle);
hash.Add(ClusterDamage);
hash.Add(Fuse);
hash.Add(FireAmount);
hash.Add(FireSpreadSpeed);
hash.Add(FireTime);
hash.Add(MeleeForce);
hash.Add(MeleeAngle);
hash.Add(MeleeDamage);
hash.Add(FirepunchHeight);
hash.Add(DragonballDamage);
hash.Add(DragonballPower);
hash.Add(DragonballAngle);
hash.Add(DragonballTime);
hash.Add(DiggingTime);
hash.Add(StrikeClusterCount);
hash.Add(BulletSpreadAngle);
return hash.ToHashCode();
}
}
}

View File

@ -1,182 +0,0 @@
using System.Runtime.InteropServices;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents the configuration of a weapon.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SchemeWeaponSetting
{
/// <summary>
/// The amount of this weapon with which a team is equipped at game start. 10 and negative values represent
/// infinity.
/// </summary>
public int Ammunition;
/// <summary>
/// The number of turns required to be taken by each team before this weapon becomes available.
/// </summary>
public int Delay;
/// <summary>
/// Retreat time after using this weapon. 0 uses the setting from the game options.
/// </summary>
public int RetreatTime;
/// <summary>
/// <c>true</c> to preselect this weapon in the next turn; otherwise <c>false</c>.
/// </summary>
public bool Remember;
/// <summary>
/// An unused field with unknown value.
/// </summary>
public int Unused1;
/// <summary>
/// The amount of this weapon added to the team armory when collected from a crate.
/// </summary>
public int CrateAmmunition;
/// <summary>
/// The amount of bullets shot at once.
/// </summary>
public int BulletCount;
/// <summary>
/// The percentual chance of this weapon to appear in crates.
/// </summary>
public int Probability;
/// <summary>
/// The damage measured in health points which also determines the blast radius.
/// </summary>
public int Damage;
/// <summary>
/// The pushing power measured in percent.
/// </summary>
public int ExplosionPower;
/// <summary>
/// The offset to the bottom of an explosion, measured in percent.
/// </summary>
public int ExplosionBias;
/// <summary>
/// The milliseconds required before this weapon starts flying towards its target.
/// </summary>
public int HomingDelay;
/// <summary>
/// The length in milliseconds this weapon flies towards its target before giving up.
/// </summary>
public int HomingTime;
/// <summary>
/// The percentual amount this weaopn is affected by wind.
/// </summary>
public int WindResponse;
/// <summary>
/// An unused field with unknown value.
/// </summary>
public int Unused2;
/// <summary>
/// The number of clusters into which this weapon explodes.
/// </summary>
public int ClusterCount;
/// <summary>
/// The speed in which clusters are dispersed in percent.
/// </summary>
public int ClusterLaunchPower;
/// <summary>
/// The angle in which clusters are dispersed in degrees.
/// </summary>
public int ClusterLaunchAngle;
/// <summary>
/// The damage of clusters measured in health points which also determines the blast radius.
/// </summary>
public int ClusterDamage;
/// <summary>
/// Overrides the fuse of this weapon, 0 for default.
/// </summary>
public int Fuse;
/// <summary>
/// The amount of fire created.
/// </summary>
public int FireAmount;
/// <summary>
/// The speed in which fire spreads, measured in percent.
/// </summary>
public int FireSpreadSpeed;
/// <summary>
/// The period in which fire burns, measured in percent.
/// </summary>
public int FireTime;
/// <summary>
/// The melee impact force in percent.
/// </summary>
public int MeleeForce;
/// <summary>
/// The melee impact angle in degrees.
/// </summary>
public int MeleeAngle;
/// <summary>
/// The melee damage in health points.
/// </summary>
public int MeleeDamage;
/// <summary>
/// The height of the fire punch jump, measured in percent.
/// </summary>
public int FirepunchHeight;
/// <summary>
/// The damage a dragon ball causes, measured in health points.
/// </summary>
public int DragonballDamage;
/// <summary>
/// The power in which a dragon ball launches hit worms, measured in percent.
/// </summary>
public int DragonballPower;
/// <summary>
/// The angle in which a dragon ball launches hit worms, measured in degrees.
/// </summary>
public int DragonballAngle;
/// <summary>
/// The life time of a launched dragon ball measured in milliseconds.
/// </summary>
public int DragonballTime;
/// <summary>
/// The length of digging measured in milliseconds. Applies to Kamikaze and digging tools.
/// </summary>
public int DiggingTime;
/// <summary>
/// The amount of airstrike clusters thrown.
/// </summary>
public int StrikeClusterCount;
/// <summary>
/// The angle in which bullets are dispersed, measured in degrees.
/// </summary>
public int BulletSpreadAngle;
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
@ -9,7 +11,7 @@ namespace Syroot.Worms.Worms2
/// Represents scheme weapons stored in an WEP file which contains armory configuration.
/// Used by W2. S. https://worms2d.info/Weapons_file.
/// </summary>
public class SchemeWeapons : ILoadableFile, ISaveableFile
public class SchemeWeapons : ILoadableFile, ISaveableFile, IEquatable<SchemeWeapons?>
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
@ -22,7 +24,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class.
/// </summary>
public SchemeWeapons() => Weapons = new SchemeWeaponSetting[_weaponCount];
public SchemeWeapons() { }
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given
@ -40,13 +42,24 @@ namespace Syroot.Worms.Worms2
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the array of <see cref="SchemeWeaponSetting"/> instances, each mapping to one weapon at the index of
/// the <see cref="SchemeWeapon"/> enumeration.
/// Gets the array of <see cref="SchemeWeapon"/> instances, each mapping to one weapon at the index of
/// the <see cref="Weapon"/> enumeration.
/// </summary>
public SchemeWeaponSetting[] Weapons { get; set; }
public SchemeWeapon[] Weapons { get; set; } = new SchemeWeapon[_weaponCount];
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override bool Equals(object? obj) => Equals(obj as SchemeWeapons);
/// <inheritdoc/>
public bool Equals(SchemeWeapons? other)
=> other != null
&& Weapons.SequenceEqual(other.Weapons);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(Weapons);
/// <inheritdoc/>
public void Load(Stream stream)
{
@ -58,9 +71,9 @@ namespace Syroot.Worms.Worms2
throw new InvalidDataException("Invalid WEP file signature.");
// Read the weapon settings.
Weapons = new SchemeWeaponSetting[_weaponCount];
Weapons = new SchemeWeapon[_weaponCount];
for (int i = 0; i < _weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeaponSetting>();
Weapons[i] = reader.ReadStruct<SchemeWeapon>();
}
/// <inheritdoc/>
@ -80,7 +93,7 @@ namespace Syroot.Worms.Worms2
writer.Write(_signature, StringCoding.ZeroTerminated);
// Write the weapon settings.
foreach (SchemeWeaponSetting weapon in Weapons)
foreach (SchemeWeapon weapon in Weapons)
writer.WriteStruct(weapon);
}

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.Worms2</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms 2.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms 2</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,7 +1,8 @@
using System;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
@ -17,17 +18,17 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets the name of the team.
/// </summary>
public string Name { get; set; }
public string Name { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the name of soundbank for the voice of team worms.
/// </summary>
public string SoundBankName { get; set; }
public string SoundBankName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the 8 worm names.
/// Gets the 8 worm names.
/// </summary>
public string[] WormNames { get; set; }
public string[] WormNames { get; } = new string[8];
public int Unknown2 { get; set; }
public int Unknown3 { get; set; }
@ -110,7 +111,8 @@ namespace Syroot.Worms.Worms2
Unknown1 = reader.ReadInt16();
Name = reader.ReadString(66);
SoundBankName = reader.ReadString(36);
WormNames = reader.ReadStrings(8, 20);
for (int i = 0; i < WormNames.Length; i++)
WormNames[i] = reader.ReadString(20);
Unknown2 = reader.ReadInt32();
Unknown3 = reader.ReadInt32();
Unknown4 = reader.ReadInt32();
@ -155,9 +157,10 @@ namespace Syroot.Worms.Worms2
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.Write(Unknown1);
writer.WriteString(Name, 66);
writer.WriteString(SoundBankName, 36);
writer.WriteStrings(WormNames, 20);
writer.WriteFixedString(Name, 66);
writer.WriteFixedString(SoundBankName, 36);
for (int i = 0; i < 8; i++)
writer.WriteFixedString(WormNames[i], 20);
writer.Write(Unknown2);
writer.Write(Unknown3);
writer.Write(Unknown4);

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
@ -17,7 +17,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() => Teams = new List<Team>();
public TeamContainer() { }
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given
@ -37,7 +37,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public List<Team> Teams { get; set; }
public IList<Team> Teams { get; set; } = new List<Team>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------

View File

@ -1,9 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms
{
@ -13,7 +14,7 @@ namespace Syroot.Worms
/// optimal performance when accessing and manipulating the directory.
/// Used by W2, WA and WWP. S. https://worms2d.info/Graphics_directory.
/// </summary>
public class Archive : Dictionary<string, byte[]>, ILoadableFile, ISaveableFile
public class Archive : IDictionary<string, byte[]>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
@ -23,6 +24,10 @@ namespace Syroot.Worms
private const int _hashBits = 10;
private const int _hashSize = 1 << _hashBits;
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly IDictionary<string, byte[]> _entries = new Dictionary<string, byte[]>();
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
@ -43,8 +48,42 @@ namespace Syroot.Worms
/// <param name="fileName">The name of the file to load the data from.</param>
public Archive(string fileName) => Load(fileName);
// ---- OPERATORS ----------------------------------------------------------------------------------------------
/// <inheritdoc/>
public byte[] this[string key]
{
get => _entries[key];
set => _entries[key] = value;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <inheritdoc/>
public ICollection<string> Keys => _entries.Keys;
/// <inheritdoc/>
public ICollection<byte[]> Values => _entries.Values;
/// <inheritdoc/>
public int Count => _entries.Count;
/// <inheritdoc/>
bool ICollection<KeyValuePair<string, byte[]>>.IsReadOnly => false;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Add(string key, byte[] value) => _entries.Add(key, value);
/// <inheritdoc/>
public void Clear() => _entries.Clear();
/// <inheritdoc/>
public bool ContainsKey(string key) => _entries.ContainsKey(key);
/// <inheritdoc/>
public IEnumerator<KeyValuePair<string, byte[]>> GetEnumerator() => _entries.GetEnumerator();
/// <summary>
/// Loads the data from the given <see cref="Stream"/>.
/// </summary>
@ -100,6 +139,9 @@ namespace Syroot.Worms
Load(stream);
}
/// <inheritdoc/>
public bool Remove(string key) => _entries.Remove(key);
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
@ -132,8 +174,8 @@ namespace Syroot.Worms
}
// Write the hash table and file entries.
int tocStart = (int)writer.Position;
int fileEntryOffset = sizeof(int) + _hashSize * sizeof(int);
uint tocStart = (uint)writer.Position;
uint fileEntryOffset = sizeof(int) + _hashSize * sizeof(int);
writer.SatisfyOffset(tocOffset, tocStart);
writer.Write(_tocSignature);
for (int i = 0; i < _hashSize; i++)
@ -158,9 +200,9 @@ namespace Syroot.Worms
writer.Write(entry.Name, StringCoding.ZeroTerminated);
writer.Align(4);
if (j < entries.Count - 1)
writer.SatisfyOffset(nextEntryOffset, (int)writer.Position - tocStart);
writer.SatisfyOffset(nextEntryOffset, (uint)writer.Position - tocStart);
}
fileEntryOffset = (int)writer.Position - tocStart;
fileEntryOffset = (uint)writer.Position - tocStart;
}
}
}
@ -178,6 +220,9 @@ namespace Syroot.Worms
Save(stream);
}
/// <inheritdoc/>
public bool TryGetValue(string key, out byte[] value) => _entries.TryGetValue(key, out value);
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private int CalculateHash(string name)
@ -192,7 +237,24 @@ namespace Syroot.Worms
return hash;
}
// ---- STRUCTURES ---------------------------------------------------------------------------------------------
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <inheritdoc/>
void ICollection<KeyValuePair<string, byte[]>>.Add(KeyValuePair<string, byte[]> item) => _entries.Add(item);
/// <inheritdoc/>
bool ICollection<KeyValuePair<string, byte[]>>.Contains(KeyValuePair<string, byte[]> item) => _entries.Contains(item);
/// <inheritdoc/>
void ICollection<KeyValuePair<string, byte[]>>.CopyTo(KeyValuePair<string, byte[]>[] array, int arrayIndex) => _entries.CopyTo(array, arrayIndex);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _entries.GetEnumerator();
/// <inheritdoc/>
bool ICollection<KeyValuePair<string, byte[]>>.Remove(KeyValuePair<string, byte[]> item) => _entries.Remove(item);
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
private struct HashTableEntry
{

View File

@ -31,7 +31,7 @@ namespace Syroot.Worms.Core
/// </summary>
/// <param name="self">The extended <see cref="Byte"/> instance.</param>
/// <param name="index">The 0-based index of the bit to check.</param>
/// <returns><c>true</c> when the bit is set; otherwise <c>false</c>.</returns>
/// <returns><see langword="true"/> when the bit is set; otherwise <see langword="false"/>.</returns>
public static bool GetBit(this byte self, int index) => (self & (1 << index)) != 0;
/// <summary>
@ -62,7 +62,7 @@ namespace Syroot.Worms.Core
/// </summary>
/// <param name="self">The extended <see cref="Byte"/> instance.</param>
/// <param name="index">The 0-based index of the bit to enable or disable.</param>
/// <param name="enable"><c>true</c> to enable the bit; otherwise <c>false</c>.</param>
/// <param name="enable"><see langword="true"/> to enable the bit; otherwise <see langword="false"/>.</param>
/// <returns>The current byte with the bit enabled or disabled.</returns>
public static byte SetBit(this byte self, int index, bool enable)
=> enable ? EnableBit(self, index) : DisableBit(self, index);

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace Syroot.Worms.Core.Graphics
{
/// <summary>
/// Represents a collection of methods helping with palettized images.
/// </summary>
public static class BitmapTools
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Creates an indexed <see cref="Bitmap"/> with the given <paramref name="size"/> and
/// <paramref name="palette"/> from the provided raw <paramref name="data"/> array.
/// </summary>
/// <param name="size">The dimensions of the image.</param>
/// <param name="palette">The palette as a <see cref="Color"/> array.</param>
/// <param name="data">The data array storing bytes indexing the palette color array.</param>
/// <returns>The <see cref="Bitmap"/> instance.</returns>
public static unsafe Bitmap CreateIndexed(Size size, IList<Color> palette, byte[] data)
{
// Transfer the pixel data, respecting power-of-2 strides.
Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
for (int y = 0; y < size.Height; y++)
data.AsSpan(y * size.Width, size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride));
bitmap.UnlockBits(bitmapData);
// Transfer the palette.
ColorPalette bitmapPalette = bitmap.Palette;
for (int i = 0; i < palette.Count; i++)
bitmapPalette.Entries[i] = palette[i];
bitmap.Palette = bitmapPalette;
return bitmap;
}
}
}

View File

@ -1,265 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Syroot.BinaryData;
namespace Syroot.Worms.Core.IO
{
/// <summary>
/// Represents extension methods for <see cref="BinaryStream"/> instances.
/// </summary>
[DebuggerStepThrough]
public static partial class BinaryStreamExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// ---- Reading ----
/// <summary>
/// Reads an <see cref="ILoadable"/> instance from the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The <see cref="ILoadable"/> instance.</returns>
public static T Load<T>(this BinaryStream self) where T : ILoadable, new()
{
T instance = new T();
instance.Load(self.BaseStream);
return instance;
}
/// <summary>
/// Reads <see cref="ILoadable"/> instances from the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of instances to read.</param>
/// <returns>The <see cref="ILoadable"/> instances.</returns>
public static T[] Load<T>(this BinaryStream self, int count) where T : ILoadable, new()
{
T[] instances = new T[count];
for (int i = 0; i < count; i++)
instances[i] = Load<T>(self);
return instances;
}
/// <summary>
/// Reads an RGBA 32-bit color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The read color.</returns>
public static Color ReadColor(this BinaryStream self) => Color.FromArgb(self.ReadInt32());
/// <summary>
/// Reads <paramref name="count"/> RGBA 32-bit colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <returns>The read colors.</returns>
public static Color[] ReadColors(this BinaryStream self, int count)
{
Color[] values = new Color[count];
for (int i = 0; i < count; i++)
values[i] = Color.FromArgb(self.ReadInt32());
return values;
}
/// <summary>
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
/// <returns>The read string.</returns>
public static string ReadString(this BinaryStream self, int length)
{
// TODO: This may not work with multi-byte encodings.
// Ensure to not try to decode any bytes after the 0 termination.
byte[] bytes = self.ReadBytes(length);
while (bytes[--length] == 0 && length > 0) { }
return self.Encoding.GetString(bytes, 0, length + 1);
}
/// <summary>
/// Reads <paramref name="count"/> 0-terminated strings which are stored in a fixed-size block of
/// <paramref name="length"/> bytes.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
/// <returns>The read strings.</returns>
public static string[] ReadStrings(this BinaryStream self, int count, int length)
{
string[] strings = new string[count];
for (int i = 0; i < count; i++)
strings[i] = ReadString(self, length);
return strings;
}
/// <summary>
/// Reads a raw byte structure from the current stream and returns it.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The structure of type <typeparamref name="T"/>.</returns>
public static T ReadStruct<T>(this BinaryStream self) where T : unmanaged
{
// Read the raw bytes of the structure.
#if NETSTANDARD2_0
byte[] bytes = new byte[Unsafe.SizeOf<T>()];
self.Read(bytes, 0, bytes.Length);
#else
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<T>()];
self.Read(bytes);
#endif
// Convert them to a structure instance and return it.
ReadOnlySpan<T> span = MemoryMarshal.Cast<byte, T>(bytes);
return span[0];
}
/// <summary>
/// Reads raw byte structures from the current stream and returns them.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <returns>The structures of type <typeparamref name="T"/>.</returns>
public static T[] ReadStructs<T>(this BinaryStream self, int count) where T : unmanaged
{
// Read the raw bytes of the structures.
#if NETSTANDARD2_0
byte[] bytes = new byte[Unsafe.SizeOf<T>()];
self.Read(bytes, 0, bytes.Length);
#else
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<T>() * count];
self.Read(bytes);
#endif
// Convert them to a structure array and return it.
ReadOnlySpan<T> span = MemoryMarshal.Cast<byte, T>(bytes);
return span.ToArray();
}
/// <summary>
/// Returns the current address of the stream to which a 4-byte placeholder has been written which can be filled
/// later.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The address at which a 4-byte placeholder has been written to.</returns>
public static uint ReserveOffset(this BinaryStream self)
{
uint offset = (uint)self.Position;
self.WriteUInt32(0);
return offset;
}
// ---- Writing ----
/// <summary>
/// Writes the given <see cref="ISaveable"/> instance into the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The instance to write into the current stream.</param>
public static void Save<T>(this BinaryStream self, T value) where T : ISaveable
{
value.Save(self.BaseStream);
}
/// <summary>
/// Writes the given <see cref="ISaveable"/> instances into the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The instances to write into the current stream.</param>
public static void Save<T>(this BinaryStream self, IList<T> values) where T : ISaveable
{
foreach (T value in values)
Save(self, value);
}
public static void SatisfyOffset(this BinaryStream self, uint offset, int value)
{
using (self.TemporarySeek(offset, SeekOrigin.Begin))
self.WriteInt32(value);
}
/// <summary>
/// Writes the given color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The color to write.</param>
public static void WriteColor(this BinaryStream self, Color color)
=> self.Write(color.R << 24 | color.G << 16 | color.B << 8 | color.A);
/// <summary>
/// Writes the given colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The colors to write.</param>
public static void WriteColors(this BinaryStream self, IEnumerable<Color> colors)
{
foreach (Color color in colors)
WriteColor(self, color);
}
/// <summary>
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The string to write.</param>
/// <param name="length">The number of bytes the fixed-size block takes.</param>
public static void WriteString(this BinaryStream self, string value, int length)
{
// TODO: This may not work with multi-byte encodings.
byte[] bytes = new byte[length];
if (value != null)
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0);
self.WriteBytes(bytes);
}
/// <summary>
/// Writes the given strings into fixed-size blocks of <paramref name="length"/> bytes and 0-terminates them.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The strings to write.</param>
/// <param name="length">The number of bytes a fixed-size block takes.</param>
public static void WriteStrings(this BinaryStream self, IEnumerable<string> values, int length)
{
foreach (string value in values)
WriteString(self, value, length);
}
/// <summary>
/// Writes the bytes of a structure into the current stream.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The structure to write.</param>
public static unsafe void WriteStruct<T>(this BinaryStream self, T value) where T : unmanaged
{
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>());
#if NETSTANDARD2_0
self.Write(bytes.ToArray()); // cannot prevent copy in .NET Standard 2.0
#else
self.Write(bytes);
#endif
}
/// <summary>
/// Writes the bytes of structures into the current stream.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The structures to write.</param>
public static void WriteStructs<T>(this BinaryStream self, IEnumerable<T> values) where T : unmanaged
{
foreach (T value in values)
WriteStruct(self, value);
}
}
}

View File

@ -1,6 +1,7 @@
using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
namespace Syroot.Worms.Core.Graphics
namespace Syroot.Worms.Graphics
{
/// <summary>
/// Represents an interface for any class storing indexed image palette colors.
@ -12,6 +13,6 @@ namespace Syroot.Worms.Core.Graphics
/// <summary>
/// Gets or sets the <see cref="Color"/> values stored by this palette.
/// </summary>
Color[] Colors { get; set; }
IList<Color> Colors { get; set; }
}
}

View File

@ -20,7 +20,7 @@ namespace Syroot.Worms
/// <summary>
/// Gets or sets the colors in the palette of the bitmap, if it has one.
/// </summary>
public IList<Color> Palette { get; set; }
public IList<Color>? Palette { get; set; }
/// <summary>
/// Gets or sets the size of the image in pixels.
@ -30,7 +30,7 @@ namespace Syroot.Worms
/// <summary>
/// Gets or sets the data of the image pixels.
/// </summary>
public byte[] Data { get; set; }
public byte[] Data { get; set; } = Array.Empty<byte>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -40,20 +40,44 @@ namespace Syroot.Worms
/// <returns>The <see cref="Bitmap"/> created from the raw data.</returns>
public unsafe Bitmap ToBitmap()
{
// Create bitmap with appropriate pixel format.
PixelFormat pixelFormat = BitsPerPixel switch
{
1 => PixelFormat.Format1bppIndexed,
8 => PixelFormat.Format8bppIndexed,
32 => PixelFormat.Format32bppRgb,
_ => throw new NotSupportedException($"Cannot convert to {BitsPerPixel}bpp bitmap.")
};
Bitmap bitmap = new Bitmap(Size.Width, Size.Height, pixelFormat);
// Transfer the pixel data, respecting power-of-2 strides.
Bitmap bitmap = new Bitmap(Size.Width, Size.Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
ImageLockMode.WriteOnly, pixelFormat);
float bytesPerPixel = BitsPerPixel / 8f;
Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
for (int y = 0; y < Size.Height; y++)
Data.AsSpan(y * Size.Width, Size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride));
{
Data.AsSpan((int)(y * Size.Width * bytesPerPixel), (int)(Size.Width * bytesPerPixel))
.CopyTo(bitmapSpan.Slice((int)(y * bitmapData.Stride * bytesPerPixel)));
}
bitmap.UnlockBits(bitmapData);
// Transfer the palette.
ColorPalette bitmapPalette = bitmap.Palette;
for (int i = 0; i < Palette.Count; i++)
bitmapPalette.Entries[i] = Palette[i];
bitmap.Palette = bitmapPalette;
// Transfer any palette.
switch (pixelFormat)
{
case PixelFormat.Format1bppIndexed:
ColorPalette palette1bpp = bitmap.Palette;
palette1bpp.Entries[0] = Color.Black;
palette1bpp.Entries[1] = Color.White;
bitmap.Palette = palette1bpp;
break;
case PixelFormat.Format8bppIndexed:
ColorPalette palette8bpp = bitmap.Palette;
for (int i = 0; i < Palette!.Count; i++)
palette8bpp.Entries[i] = Palette[i];
bitmap.Palette = palette8bpp;
break;
}
return bitmap;
}

View File

@ -1,7 +1,7 @@
using System;
using System.IO;
namespace Syroot.Worms.Core
namespace Syroot.Worms.Graphics
{
/// <summary>
/// Represents methods to decompress data which is compressed using Team17's internal compression algorithm for
@ -17,7 +17,7 @@ namespace Syroot.Worms.Core
/// </summary>
/// <param name="bytes">The data to compress.</param>
/// <returns>The compressed data.</returns>
internal static byte[] Compress(byte[] bytes)
internal static byte[] Compress(ReadOnlySpan<byte> bytes)
=> throw new NotImplementedException("Compressing data has not been implemented yet.");
/// <summary>
@ -25,10 +25,9 @@ namespace Syroot.Worms.Core
/// <paramref name="buffer"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the data from.</param>
/// <param name="buffer">The byte array buffer to write the decompressed data to.</param>
internal static void Decompress(Stream stream, byte[] buffer)
/// <param name="buffer">The byte buffer to write the decompressed data to.</param>
internal static void Decompress(Stream stream, Span<byte> buffer)
{
// TODO: This fails for compressed data in CD:\Data\Mission\Training0-9.img.
int output = 0; // Offset of next write.
int cmd;
while ((cmd = stream.ReadByte()) != -1)
@ -41,8 +40,7 @@ namespace Syroot.Worms.Core
}
else
{
// Arg1 = bits 2-5
int arg1 = (cmd >> 3) & 0xF;
int arg1 = cmd >> 3 & 0b1111; // bits 2-5
int arg2 = stream.ReadByte();
if (arg2 == -1)
return;
@ -68,9 +66,46 @@ namespace Syroot.Worms.Core
}
}
// Own Span reimplementation, has the same issues with some out-of-bounds-access maps. Supports color remap.
//internal static int Decompress(ReadOnlySpan<byte> src, Span<byte> dst, ReadOnlySpan<byte> colorMap)
//{
// int srcPos = 0;
// int dstPos = 0;
// int count, seek;
//
// while (true)
// {
// while (true)
// {
// while ((src[srcPos] & 0b1000_0000) == 0)
// dst[dstPos++] = colorMap[src[srcPos++]];
//
// if ((src[srcPos] & 0b0111_1000) == 0)
// break;
//
// count = (src[srcPos] >> 3 & 0b0000_1111) + 2;
// seek = (src[srcPos + 1] | ((src[srcPos] & 0b0000_0111) << 8)) + 1;
// while (count-- > 0)
// dst[dstPos] = dst[dstPos++ - seek];
// srcPos += 2;
// }
//
// seek = src[srcPos + 1] | (src[srcPos] & 0b0000_0111) << 8;
// if (seek == 0)
// break;
// count = src[srcPos + 2] + 18;
// while (count-- > 0)
// dst[dstPos] = dst[dstPos++ - seek];
//
// srcPos += 3;
// }
//
// return dstPos;
//}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static int CopyData(int offset, int compressedOffset, int count, byte[] buffer)
private static int CopyData(int offset, int compressedOffset, int count, Span<byte> buffer)
{
for (; count > 0; count--)
buffer[offset] = buffer[offset++ - compressedOffset];

View File

@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents extension methods for <see cref="BinaryStream"/> instances.
/// </summary>
public static partial class BinaryStreamExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// ---- Reading ----
/// <summary>
/// Reads an <see cref="ILoadable"/> instance from the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The <see cref="ILoadable"/> instance.</returns>
public static T Load<T>(this BinaryStream self) where T : ILoadable, new()
{
T instance = new T();
instance.Load(self.BaseStream);
return instance;
}
/// <summary>
/// Reads an RGBA 32-bit color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The read color.</returns>
public static Color ReadColor(this BinaryStream self) => Color.FromArgb(self.ReadInt32());
/// <summary>
/// Reads <paramref name="count"/> RGBA 32-bit colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <returns>The read colors.</returns>
public static Color[] ReadColors(this BinaryStream self, int count)
{
Color[] values = new Color[count];
for (int i = 0; i < count; i++)
values[i] = Color.FromArgb(self.ReadInt32());
return values;
}
/// <summary>
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
/// <returns>The read string.</returns>
public static string ReadFixedString(this BinaryStream self, int length)
{
// TODO: This may not work with multi-byte encodings.
// Ensure to not try to decode any bytes after the 0 termination.
byte[] bytes = self.ReadBytes(length);
for (length = 0; length < bytes.Length && bytes[length] != 0; length++) ;
return self.Encoding.GetString(bytes, 0, length);
}
/// <summary>
/// Returns the current position of the stream at which a 4-byte placeholder has been written which can be
/// filled later with <see cref="SatisfyOffset"/>.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The position at which a 4-byte placeholder has been written to.</returns>
public static uint ReserveOffset(this BinaryStream self)
{
uint offset = (uint)self.Position;
self.WriteUInt32(0);
return offset;
}
// ---- Writing ----
/// <summary>
/// Writes the given <see cref="ISaveable"/> instance into the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The instance to write into the current stream.</param>
public static void Save<T>(this BinaryStream self, T value) where T : ISaveable
{
value.Save(self.BaseStream);
}
/// <summary>
/// Writes the given <paramref name="value"/> to the given <paramref name="offset"/>. This is meant to be used
/// in combination with <see cref="ReserveOffset"/>.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="offset">The position at which to write the value.</param>
/// <param name="value">The value to write.</param>
public static void SatisfyOffset(this BinaryStream self, uint offset, uint value)
{
using var _ = self.TemporarySeek(offset, SeekOrigin.Begin);
self.WriteUInt32(value);
}
/// <summary>
/// Writes the given color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The color to write.</param>
public static void WriteColor(this BinaryStream self, Color color)
=> self.Write(color.A << 24 | color.R << 16 | color.G << 8 | color.B);
/// <summary>
/// Writes the given colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The colors to write.</param>
public static void WriteColors(this BinaryStream self, IEnumerable<Color> colors)
{
foreach (Color color in colors)
WriteColor(self, color);
}
/// <summary>
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The string to write.</param>
/// <param name="length">The number of bytes the fixed-size block takes.</param>
public static void WriteFixedString(this BinaryStream self, string value, int length)
{
// TODO: This may not work with multi-byte encodings.
byte[] bytes = new byte[length];
if (value != null)
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0);
self.WriteBytes(bytes);
}
}
}

View File

@ -1,6 +1,6 @@
using System.IO;
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents data which can be loaded by providing a <see cref="Stream"/> to read from.

View File

@ -1,4 +1,4 @@
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents a file which can be loaded by providing a file name.

View File

@ -1,6 +1,6 @@
using System.IO;
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents data which can be saved by providing a <see cref="Stream "/>to write to.

View File

@ -1,4 +1,4 @@
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents a file which can be saved by providing a file name.

View File

@ -4,7 +4,7 @@ using System.IO;
using System.Reflection;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Core.Riff
{
@ -82,10 +82,10 @@ namespace Syroot.Worms.Core.Riff
chunkSaver.Value.Invoke(this, new object[] { writer });
writer.SatisfyOffset(chunkSizeOffset, (int)(writer.Position - chunkSizeOffset - 4));
writer.SatisfyOffset(chunkSizeOffset, (uint)(writer.Position - chunkSizeOffset - 4));
}
writer.SatisfyOffset(fileSizeOffset, (int)(writer.Position - 8));
writer.SatisfyOffset(fileSizeOffset, (uint)(writer.Position - 8));
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
@ -102,8 +102,6 @@ namespace Syroot.Worms.Core.Riff
typeData.FileIdentifier = typeInfo.GetCustomAttribute<RiffFileAttribute>().Identifier;
// Get the chunk loading and saving handlers.
typeData.ChunkLoaders = new Dictionary<string, MethodInfo>();
typeData.ChunkSavers = new Dictionary<string, MethodInfo>();
foreach (MethodInfo method in typeInfo.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
{
RiffChunkLoadAttribute loadAttribute = method.GetCustomAttribute<RiffChunkLoadAttribute>();
@ -137,9 +135,9 @@ namespace Syroot.Worms.Core.Riff
private class TypeData
{
internal string FileIdentifier;
internal Dictionary<string, MethodInfo> ChunkLoaders;
internal Dictionary<string, MethodInfo> ChunkSavers;
internal string FileIdentifier = String.Empty;
internal IDictionary<string, MethodInfo> ChunkLoaders = new Dictionary<string, MethodInfo>();
internal IDictionary<string, MethodInfo> ChunkSavers = new Dictionary<string, MethodInfo>();
}
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents extension methods for <see cref="Stream"/> instances.
/// </summary>
public static class StreamExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Reads the unmanaged representation of a struct of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to read.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to read with.</param>
/// <returns>The read value.</returns>
public static T ReadStruct<T>(this Stream stream) where T : unmanaged
{
Span<T> span = stackalloc T[1];
stream.Read(MemoryMarshal.Cast<T, byte>(span));
return span[0];
}
/// <summary>
/// Reads the unmanaged representations of structs of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the structs to read.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to read with.</param>
/// <param name="span">A span receiving the read instances.</param>
public static void ReadStructs<T>(this Stream stream, Span<T> span) where T : unmanaged
=> stream.Read(MemoryMarshal.Cast<T, byte>(span));
/// <summary>
/// Writes the unmanaged representation of a struct of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to write.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="value">The value to write.</param>
public static unsafe void WriteStruct<T>(this Stream stream, T value) where T : unmanaged
=> stream.Write(new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>()));
/// <summary>
/// Writes the unmanaged representation of a struct of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to write.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="value">The value to write.</param>
public static unsafe void WriteStruct<T>(this Stream stream, ref T value) where T : unmanaged
=> stream.Write(new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>()));
/// <summary>
/// Writes the unmanaged representations of structs of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to write.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="span">The values to write.</param>
public static unsafe void WriteStructs<T>(this Stream stream, ReadOnlySpan<T> span) where T : unmanaged
=> stream.Write(MemoryMarshal.Cast<T, byte>(span));
// ---- Backports ----
#if NETSTANDARD2_0
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the
/// position within the stream by the number of bytes read.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced
/// by the bytes read from the current source.</param>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes allocated
/// in the buffer if that many bytes are not currently available, or zero (0) if the end of the stream has been
/// reached.</returns>
/// <remarks>
/// This .NET Standard 2.0 backport requires a temporary copy.
/// </remarks>
public static int Read(this Stream stream, Span<byte> buffer)
{
byte[] bytes = new byte[buffer.Length];
int bytesRead = stream.Read(bytes);
bytes.AsSpan(0, bytesRead).CopyTo(buffer);
return bytesRead;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the
/// current position within this stream by the number of bytes written.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance.</param>
/// <param name="value">A region of memory. This method copies the contents of this region to the current
/// stream.</param>
/// <remarks>
/// This .NET Standard 2.0 backport requires a temporary copy.
/// </remarks>
public static void Write(this Stream stream, ReadOnlySpan<byte> value) => stream.Write(value.ToArray());
#endif
}
}

View File

@ -1,22 +1,24 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.Core.IO;
using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms
{
/// <summary>
/// Represents a (palettized) graphical image stored in an IMG file, possibly compressed.
/// Represents a (palletized) graphical image stored in an IMG file, possibly compressed.
/// Used by W2, WA and WWP. S. https://worms2d.info/Image_file.
/// </summary>
public class Img : RawBitmap, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A474D49; // "IMG", 0x1A
/// <summary>Magic value identifying the start of IMG data.</summary>
public const uint Signature = 0x1A474D49; // "IMG\x1A"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -43,15 +45,20 @@ namespace Syroot.Worms
/// <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public Img(Stream stream, bool alignData) => Load(stream, alignData);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value indicating data was compressed or should be compressed when saving.
/// </summary>
public bool Compressed { get; private set; }
/// <summary>
/// Gets an optional description of the image contents.
/// </summary>
public string Description { get; private set; }
public string? Description { get; private set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -59,12 +66,16 @@ namespace Syroot.Worms
/// Loads the data from the given <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(Stream stream) => Load(stream, false);
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
@ -75,13 +86,15 @@ namespace Syroot.Worms
/// Loads the data from the given <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(Stream stream, bool alignData)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
if (reader.ReadUInt32() != Signature)
throw new InvalidDataException("Invalid IMG file signature.");
int fileSize = reader.ReadInt32();
@ -109,10 +122,10 @@ namespace Syroot.Worms
if (flags.HasFlag(Flags.Palettized))
{
int colorCount = reader.ReadInt16();
Palette = new Color[colorCount + 1];
Palette[0] = Color.Black;
for (int i = 1; i <= colorCount; i++)
Palette[i] = Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte());
Palette = new List<Color>(colorCount + 1);
Palette.Add(Color.Black);
while (colorCount-- > 0)
Palette.Add(Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()));
}
else
{
@ -125,20 +138,26 @@ namespace Syroot.Worms
// Read the data byte array, which might be compressed or aligned.
if (alignData)
reader.Align(4);
byte[] data = new byte[Size.Width * Size.Height * BitsPerPixel / 8];
int dataSize = (int)(BitsPerPixel / 8f * Size.Width * Size.Height);
if (flags.HasFlag(Flags.Compressed))
Team17Compression.Decompress(reader.BaseStream, data);
{
// Some shipped images require up to 3 bytes to not fail when decompressing with out-of-bounds access.
Data = new byte[dataSize];
Team17Compression.Decompress(reader.BaseStream, Data);
}
else
data = reader.ReadBytes(data.Length);
Data = data;
{
Data = reader.ReadBytes(dataSize);
}
}
/// <summary>
/// Loads the data from the given file. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(string fileName, bool alignData)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
@ -149,41 +168,25 @@ namespace Syroot.Worms
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
public void Save(Stream stream) => Save(stream, false, false);
/// <summary>
/// Saves the optionally compressed data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
public void Save(Stream stream, bool compress) => Save(stream, compress, false);
public void Save(Stream stream) => Save(stream, false);
/// <summary>
/// Saves the data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
public void Save(string fileName) => Save(fileName, false, false);
public void Save(string fileName) => Save(fileName, false);
/// <summary>
/// Saves the optionally compressed data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
public void Save(string fileName, bool compress) => Save(fileName, compress, false);
/// <summary>
/// Saves the optionally compressed data into the given <paramref name="stream"/>. The data block can be aligned
/// to a 4-bte boundary.
/// Saves the data into the given <paramref name="stream"/>. The data block can be aligned to a 4-byte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public void Save(Stream stream, bool compress, bool alignData)
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public void Save(Stream stream, bool alignData)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
writer.Write(Signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write an optional string describing the image contents and the bits per pixel.
@ -195,7 +198,7 @@ namespace Syroot.Worms
Flags flags = Flags.None;
if (Palette != null)
flags |= Flags.Palettized;
if (compress)
if (Compressed)
flags |= Flags.Compressed;
writer.WriteEnum(flags, true);
@ -220,23 +223,22 @@ namespace Syroot.Worms
if (alignData)
writer.Align(4);
byte[] data = Data;
if (compress)
if (Compressed)
data = Team17Compression.Compress(data);
writer.Write(data);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
}
/// <summary>
/// Saves the optionally compressed data in the given file. The data block can be aligned to a 4-byte boundary.
/// Saves the data in the given file. The data block can be aligned to a 4-byte boundary.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public void Save(string fileName, bool compress, bool alignData)
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public void Save(string fileName, bool alignData)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream, compress, alignData);
Save(stream, alignData);
}
// ---- ENUMERATIONS -------------------------------------------------------------------------------------------
@ -244,7 +246,7 @@ namespace Syroot.Worms
[Flags]
private enum Flags : byte
{
None = 0,
None,
Compressed = 1 << 6,
Palettized = 1 << 7
}

View File

@ -1,42 +0,0 @@
namespace Syroot.Worms
{
/// <summary>
/// Represents the map terrain configuration found in LEV and BIT files.
/// </summary>
public class LevelInfo
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public uint LandSeed { get; set; }
public uint ObjectSeed { get; set; }
public bool Cavern { get; set; }
public LandStyle Style { get; set; }
public bool NoBorder { get; set; }
public int ObjectPercent { get; set; }
public int BridgePercent { get; set; }
public int WaterLevel { get; set; }
public int SoilIndex { get; set; }
public int WaterColor { get; set; }
}
public enum LandStyle : int
{
SingleIsland,
DoubleIsland,
SingleSmallIsland,
DoubleSmallIsland,
Cavern,
SingleTunnel,
CavernWater,
DoubleTunnel
}
}

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.Graphics;
using Syroot.Worms.Core.IO;
using Syroot.Worms.Core.Riff;
using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms
{
@ -23,7 +25,7 @@ namespace Syroot.Worms
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> class.
/// </summary>
public RiffPalette() => Version = _version;
public RiffPalette() { }
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given
@ -43,27 +45,27 @@ namespace Syroot.Worms
/// <summary>
/// Gets the version of the palette data.
/// </summary>
public int Version { get; set; }
public int Version { get; set; } = _version;
/// <summary>
/// Gets the <see cref="Color"/> instances stored in this palette.
/// Gets or sets the <see cref="Color"/> instances stored in this palette.
/// </summary>
public Color[] Colors { get; set; }
public IList<Color> Colors { get; set; } = new List<Color>();
/// <summary>
/// Gets the unknown data in the offl chunk.
/// </summary>
public byte[] OfflData { get; set; }
public byte[] OfflData { get; set; } = Array.Empty<byte>();
/// <summary>
/// Gets the unknown data in the tran chunk.
/// </summary>
public byte[] TranData { get; set; }
public byte[] TranData { get; set; } = Array.Empty<byte>();
/// <summary>
/// Gets the unknown data in the unde chunk.
/// </summary>
public byte[] UndeData { get; set; }
public byte[] UndeData { get; set; } = Array.Empty<byte>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -103,7 +105,7 @@ namespace Syroot.Worms
#pragma warning disable IDE0051 // Remove unused private members
[RiffChunkLoad("data")]
private void LoadDataChunk(BinaryStream reader, int length)
private void LoadDataChunk(BinaryStream reader, int _)
{
// Read the PAL version.
Version = reader.ReadInt16();
@ -111,10 +113,11 @@ namespace Syroot.Worms
throw new InvalidDataException("Unknown PAL version.");
// Read the colors.
Colors = new Color[reader.ReadInt16()];
for (int i = 0; i < Colors.Length; i++)
short colorCount = reader.ReadInt16();
Colors = new List<Color>();
while (colorCount-- > 0)
{
Colors[i] = Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte());
Colors.Add(Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()));
_ = reader.ReadByte(); // Dismiss alpha, as it is not used in WA.
}
}
@ -135,10 +138,9 @@ namespace Syroot.Worms
writer.Write(_version);
// Write the colors.
writer.Write((short)Colors.Length);
for (int i = 0; i < Colors.Length; i++)
writer.Write((short)Colors.Count);
foreach (Color color in Colors)
{
Color color = Colors[i];
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);

View File

@ -4,8 +4,8 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms</AssemblyName>
<Description>.NET library for loading and modifying files of Team17 Worms games.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<Version>3.2.0</Version>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.BinaryData.Serialization" Version="5.2.0" />

26
src/test.xml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Compilation -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TargetFramework>netcoreapp3</TargetFramework>
</PropertyGroup>
<!-- References -->
<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" />
</ItemGroup>
<!-- Files Linking -->
<Target Name="FilesRemove" AfterTargets="Build;Clean">
<Message Text="Removing Files" Importance="high" />
<Exec Command="RD &quot;$(OutputPath)Files&quot; /S/Q" />
</Target>
<Target Name="FilesCreate" AfterTargets="Build">
<Message Text="Linking Files" Importance="high" />
<Exec Command="MKLINK /D &quot;$(OutputPath)Files&quot; &quot;$(ProjectDir)Files&quot;" />
</Target>
</Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More