mirror of
https://gitlab.com/Syroot/Worms.git
synced 2025-04-11 03:40:05 +03:00
1485 lines
64 KiB
C#
1485 lines
64 KiB
C#
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Syroot.BinaryData;
|
|
using Syroot.Worms.Core;
|
|
using Syroot.Worms.IO;
|
|
|
|
namespace Syroot.Worms.Armageddon
|
|
{
|
|
/// <summary>
|
|
/// Represents a scheme stored in a WSC file which contains game settings and armory configuration, including
|
|
/// RubberWorm settings encoded in those.
|
|
/// Used by WA and WWP. S. https://worms2d.info/Game_scheme_file.
|
|
/// </summary>
|
|
public partial class Scheme : IEquatable<Scheme>, ILoadableFile, ISaveableFile
|
|
{
|
|
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
|
|
|
private const uint _signature = 0x4D484353; // "SCHM"
|
|
|
|
private const int _objectCount5Step = 30;
|
|
private const int _objectCount10Step = 44;
|
|
private const int _objectCountLastStep = 59;
|
|
|
|
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// The <see cref="SchemeVersion.Version3"/> or RubberWorm settings.
|
|
/// </summary>
|
|
public ExtendedOptions Extended = ExtendedOptions.Default;
|
|
|
|
private static readonly byte[] _waterRiseRates;
|
|
|
|
private int _fallDamage;
|
|
private byte _waterRiseIndex;
|
|
private byte _objectCount = 8;
|
|
private byte _mineDelay;
|
|
private byte _turnTime;
|
|
private byte _roundTimeMinutes;
|
|
private byte _roundTimeSeconds;
|
|
|
|
private ushort _rwVersion;
|
|
|
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Initializes static members of the <see cref="Scheme"/> class.
|
|
/// </summary>
|
|
static Scheme()
|
|
{
|
|
// Generate water rise rate lookup array.
|
|
_waterRiseRates = new byte[256];
|
|
for (int i = 0; i < 256; i++)
|
|
_waterRiseRates[i] = (byte)(Math.Pow(i, 2) * 5 % 256);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Scheme"/> class.
|
|
/// </summary>
|
|
public Scheme() { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Scheme"/> 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 Scheme(Stream stream) => Load(stream);
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Scheme"/> class, loading the data from the given file.
|
|
/// </summary>
|
|
/// <param name="fileName">The name of the file to load the data from.</param>
|
|
public Scheme(string fileName) => Load(fileName);
|
|
|
|
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Gets the available mine or oil drum object counts.
|
|
/// </summary>
|
|
public static IEnumerable<int> ValidObjectCounts
|
|
{
|
|
get
|
|
{
|
|
for (int i = 1; i < 30; i++)
|
|
yield return i;
|
|
for (int i = 30; i < 100; i += 5)
|
|
yield return i;
|
|
for (int i = 100; i <= 250; i += 10)
|
|
yield return i;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the available fall damage percentages.
|
|
/// </summary>
|
|
public static IEnumerable<int> ValidFallDamages
|
|
{
|
|
get
|
|
{
|
|
for (int i = 0; i <= 508; i += 4)
|
|
yield return i;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the available unique and ordered water rise rates in pixels.
|
|
/// </summary>
|
|
public static IEnumerable<byte> ValidWateRiseRates => _waterRiseRates.Distinct().OrderBy(x => x);
|
|
|
|
// ---- Header ----
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="SchemeVersion"/> of this scheme, controlling which features are stored.
|
|
/// </summary>
|
|
public SchemeVersion Version { get; set; } = SchemeVersion.Version3;
|
|
|
|
// ---- Options ----
|
|
|
|
/// <summary>
|
|
/// Gets or sets the delay in seconds between each team's turn to allow relaxed switching of seats.
|
|
/// </summary>
|
|
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
|
|
/// while standing on land.
|
|
/// </summary>
|
|
public byte RetreatTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the time in seconds available for a worm to retreat after using a weapon which ends the turn
|
|
/// while on a rope.
|
|
/// </summary>
|
|
public byte RetreatTimeRope { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the total round time until sudden death will be displayed in the
|
|
/// turn timer.
|
|
/// </summary>
|
|
public bool ShowRoundTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether significant turns will be replayed in offline games.
|
|
/// </summary>
|
|
public bool Replays { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the percentual amount of fall damage applied, relative to normal fall damage being 100%. Valid
|
|
/// values lie between 0-508, at a granularity of 4. Note that changing this value rounds it down to a valid
|
|
/// granularity.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">The value is not between 0-508.</exception>
|
|
/// <remarks>Valid settings can be enumerated through <see cref="ValidFallDamages"/>.</remarks>
|
|
public int FallDamage
|
|
{
|
|
get => _fallDamage;
|
|
set
|
|
{
|
|
if (value < 0 || value > 508)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Fall damage must be between 0-508.");
|
|
_fallDamage = value >> 2 << 2;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether worms cannot walk and are mostly stuck at their current position
|
|
/// without using any utilities.
|
|
/// </summary>
|
|
public bool ArtilleryMode { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating the scheme editor used to modify this scheme. Originally used to indicate
|
|
/// the unimplemented Bounty Mode.
|
|
/// </summary>
|
|
public SchemeEditor SchemeEditor { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating the stockpiling of armory between game rounds.
|
|
/// </summary>
|
|
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 WormSelect { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating the action triggered when Sudden Death starts.
|
|
/// </summary>
|
|
public SuddenDeathEvent SuddenDeathEvent { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the amount in pixels which the water will rise between turns after Sudden Death was triggered.
|
|
/// Note that changing this value rounds it down to a valid setting.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">The value is not between 0-253.</exception>
|
|
/// <remarks>Valid settings can be enumerated through <see cref="ValidWateRiseRates"/>.</remarks>
|
|
public byte WaterRiseRate
|
|
{
|
|
get => _waterRiseRates[_waterRiseIndex];
|
|
set
|
|
{
|
|
if (value > 253)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Water rise rate must be between 0-253.");
|
|
|
|
int smallestDeviation = Int32.MaxValue;
|
|
for (int i = 0; i < _waterRiseRates.Length; i++)
|
|
{
|
|
int deviation = value - _waterRiseRates[i];
|
|
if (deviation >= 0 && smallestDeviation > deviation)
|
|
{
|
|
smallestDeviation = deviation;
|
|
_waterRiseIndex = (byte)i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the percentual probability of a weapon crate to drop between turns. Negative values might crash
|
|
/// the game.
|
|
/// </summary>
|
|
public sbyte WeaponCrateProb { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether donor cards can spawn upon a worm's death.
|
|
/// </summary>
|
|
public bool DonorCards { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the percentual probability of a health crate to drop between turns. Negative values might crash
|
|
/// the game.
|
|
/// </summary>
|
|
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.
|
|
/// </summary>
|
|
public byte HealthCrateEnergy { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the percentual probability of a utility crate to drop between turns. Negative values might
|
|
/// crash the game.
|
|
/// </summary>
|
|
public sbyte UtilityCrateProb { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the type of objects which can be placed on the map.
|
|
/// </summary>
|
|
public MapObjectType ObjectTypes { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum number of objects (mines or oil drums) on the map. Note that changing this value
|
|
/// rounds it down to a valid setting.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">The value is not between 1-250.</exception>
|
|
/// <remarks>Valid settings can be enumerated through <see cref="ValidObjectCounts"/>.</remarks>
|
|
public byte ObjectCount
|
|
{
|
|
get => _objectCount;
|
|
set
|
|
{
|
|
if (value < 1 || value > 250)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Object count must be between 1-250.");
|
|
|
|
if (value > 100)
|
|
_objectCount = (byte)(value - value % 10);
|
|
else if (value > 30)
|
|
_objectCount = (byte)(value - value % 5);
|
|
else if (value == 0)
|
|
_objectCount = 1;
|
|
else
|
|
_objectCount = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
{
|
|
get => _mineDelay;
|
|
set
|
|
{
|
|
if (value == 4 || value > 127)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Mine delay must be between 0-127 and not be 4.");
|
|
_mineDelay = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the mine fuse will be randomly chosen between fractions of 1 to 3
|
|
/// seconds. If <see langword="true"/>, the <see cref="MineDelay"/> setting will be ignored.
|
|
/// </summary>
|
|
public bool MineDelayRandom { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether mines can refuse to explode after their count down.
|
|
/// </summary>
|
|
public bool DudMines { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether worms are placed manually by the players for their initial position
|
|
/// at round start.
|
|
/// </summary>
|
|
public bool ManualWormPlacement { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the initial worm energy at round start.
|
|
/// </summary>
|
|
public byte WormEnergy { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the turn time in seconds available for the player to move. Must be in the range of 0-127.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">Value is not between 0-127.</exception>
|
|
public byte TurnTime
|
|
{
|
|
get => _turnTime;
|
|
set
|
|
{
|
|
if (value > 127)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Turn time must be between 0-127.");
|
|
_turnTime = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the round time before sudden death is triggered between 0-127 minutes.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">Value is not between 0-127.</exception>
|
|
public byte RoundTimeMinutes
|
|
{
|
|
get => _roundTimeMinutes;
|
|
set
|
|
{
|
|
if (value > 127)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Round time must be between 0-127 minutes.");
|
|
_roundTimeMinutes = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the round time before sudden death is triggered in between 0-128 seconds. When 0,
|
|
/// <see cref="RoundTimeMinutes"/> is used instead.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">Value is not between 0-128.</exception>
|
|
public byte RoundTimeSeconds
|
|
{
|
|
get => _roundTimeSeconds;
|
|
set
|
|
{
|
|
if (value > 128)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Round time must be between 0-128 seconds.");
|
|
_roundTimeSeconds = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of round wins required to win the game. If 0, only 1 round is played, no matter the
|
|
/// outcome.
|
|
/// </summary>
|
|
public byte NumberOfWins { get; set; }
|
|
|
|
// ---- Cheats ----
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether blood effects are enabled.
|
|
/// </summary>
|
|
public bool Blood { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the Super Sheep weapon gets upgraded to the Aqua Sheep, which can
|
|
/// fly underwater.
|
|
/// </summary>
|
|
public bool AquaSheep { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether sheeps will jump out of exploding crates.
|
|
/// </summary>
|
|
public bool SheepHeaven { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether worms have infinity energy, killable only by drowning them.
|
|
/// </summary>
|
|
public bool GodWorms { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether terrain cannot be destroyed by explosions.
|
|
/// </summary>
|
|
public bool IndiLand { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the Grenade weapon is more powerful.
|
|
/// </summary>
|
|
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 UpgradeShotgun { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether cluster weapon explode into more clusters.
|
|
/// </summary>
|
|
public bool UpgradeCluster { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the Longbow weapon is more powerful.
|
|
/// </summary>
|
|
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 TeamWeapons { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether super weapons can be collected from crates.
|
|
/// </summary>
|
|
public bool SuperWeapons { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the list of <see cref="SchemeWeapon"/> instances, of which each weapon can be accessed by index or
|
|
/// <see cref="Weapon"/> name. Depending on the scheme <see cref="Version"/>, super weapon settings may not be
|
|
/// stored in the resulting scheme file.
|
|
/// </summary>
|
|
public WeaponList Weapons { get; set; } = new WeaponList();
|
|
|
|
// ---- Extended Options ----
|
|
|
|
/// <summary>
|
|
/// Gets or sets trailing bytes which could not be parsed and will be attached when saving the scheme anew.
|
|
/// These may be unknown <see cref="SchemeVersion.Version3"/> extended scheme options or WWP trash.
|
|
/// </summary>
|
|
public byte[] Attachment { get; set; } = Array.Empty<byte>();
|
|
|
|
/// <summary>
|
|
/// Gets or sets the game version to be forced. Settings this property updates all extended options to reflect
|
|
/// the effects of the forced version.
|
|
/// </summary>
|
|
/// <remarks>Configurable with the /version command in RubberWorm.
|
|
/// S. http://worms2d.info/List_of_Worms_Armageddon_logic_versions .</remarks>
|
|
public ushort RwVersion
|
|
{
|
|
get => _rwVersion;
|
|
set
|
|
{
|
|
_rwVersion = value;
|
|
|
|
// Configure V3 settings according to selected version.
|
|
void setBattyRope1To2() => Extended.BattyRope = true;
|
|
void setBattyRope3And5To9()
|
|
{
|
|
setBattyRope1To2();
|
|
Extended.RopeKnockForce = 100;
|
|
}
|
|
|
|
void set150Speed() => Extended.GameSpeed = 1.5f;
|
|
|
|
void setTestStuff1()
|
|
{
|
|
Extended.ExplosionFallDamage = true;
|
|
Extended.GirderRadiusAssist = true;
|
|
Extended.IndianRopeGlitch = true;
|
|
Extended.KeepControlHeadBump = true;
|
|
Extended.KeepControlSkim = SkimControlLoss.Kept;
|
|
Extended.RopeKnockForce = 100;
|
|
Extended.RopeRollDrops = RopeRollDrops.Rope;
|
|
Extended.SkipWalk = SkipWalk.Facilitated;
|
|
}
|
|
void setTestStuff2()
|
|
{
|
|
setTestStuff1();
|
|
Extended.BattyRope = true;
|
|
Extended.RopeRollDrops = RopeRollDrops.RopeJump;
|
|
Extended.KeepControlXImpact = XImpactControlLoss.Kept;
|
|
}
|
|
void setTestStuff3()
|
|
{
|
|
setTestStuff2();
|
|
Extended.ProjectileMaxSpeed = 0;
|
|
Extended.RopeMaxSpeed = 0;
|
|
}
|
|
void setTestStuff4()
|
|
{
|
|
setTestStuff3();
|
|
Extended.AntiLockPower = true;
|
|
Extended.CircularAim = true;
|
|
Extended.GirderRadiusAssist = false;
|
|
Extended.JetpackMaxSpeed = 5 * 2;
|
|
Extended.ProjectileMaxSpeed = 32 * 2;
|
|
Extended.RopeMaxSpeed = 16 * 2;
|
|
}
|
|
void setTestStuff5()
|
|
{
|
|
setTestStuff4();
|
|
Extended.ProjectileMaxSpeed = 0;
|
|
}
|
|
void setTestStuff6To9()
|
|
{
|
|
setTestStuff5();
|
|
Extended.GirderRadiusAssist = true;
|
|
}
|
|
|
|
void setRacingStuffPre()
|
|
{
|
|
Extended.WormPhasingAlly = WormPhasing.WormsWeapons;
|
|
Extended.WormPhasingEnemy = WormPhasing.WormsWeapons;
|
|
Extended.WormSelectKeepHotSeat = true;
|
|
}
|
|
void setRacingStuff()
|
|
{
|
|
Extended.WormPhasingAlly = WormPhasing.Worms;
|
|
Extended.WormPhasingEnemy = WormPhasing.Worms;
|
|
Extended.WormSelectKeepHotSeat = true;
|
|
}
|
|
|
|
void setBoomRacing()
|
|
{
|
|
Extended.WormPhasingEnemy = WormPhasing.WormsWeaponsDamage;
|
|
Extended.WormSelectKeepHotSeat = true;
|
|
}
|
|
|
|
switch (_rwVersion)
|
|
{
|
|
case 11: // 3.5 Beta 3pre15[BattyRope1]
|
|
case 12: // 3.5 Beta 3pre15[BattyRope2]
|
|
setBattyRope1To2();
|
|
break;
|
|
case 15: // 3.5 Beta 3pre17[BattyRope3]
|
|
case 36: // 3.6.23.0[BattyRope5]
|
|
case 63: // 3.6.27.9[BattyRope5]
|
|
case 73: // 3.6.28.0[BattyRope5]
|
|
case 86: // 3.6.29.3[BattyRope6]
|
|
case 96: // 3.6.29.20[BattyRope6]
|
|
case 102: // 3.6.29.21[BattyRope6]
|
|
case 108: // 3.6.29.22[BattyRope7]
|
|
case 115: // 3.6.29.23[BattyRope7]
|
|
case 122: // 3.6.29.24[BattyRope7]
|
|
case 138: // 3.6.29.47[BattyRope8]
|
|
case 145: // 3.6.29.49[BattyRope8]
|
|
case 154: // 3.6.30.0[BattyRope8]
|
|
case 162: // 3.6.31.0[BattyRope8]
|
|
case 183: // 3.6.31.3[BattyRope8]
|
|
case 190: // 3.6.31.5[BattyRope8]
|
|
case 197: // 3.6.31.7[BattyRope8]
|
|
case 204: // 3.6.31.12[BattyRope8]
|
|
case 211: // 3.6.31.14[BattyRope8]
|
|
case 218: // 3.6.31.22[BattyRope8]
|
|
case 225: // 3.6.31.23[BattyRope8]
|
|
case 232: // 3.6.31.31[BattyRope9]
|
|
case 239: // 3.7.0.0 RC1-3[BattyRope9]
|
|
case 246: // 3.7.0.0[BattyRope9]
|
|
case 253: // 3.7.0.2[BattyRope9]
|
|
case 260: // 3.7.0.3[BattyRope9]
|
|
case 267: // 3.7.0.4[BattyRope9]
|
|
case 274: // 3.7.0.5[BattyRope9]
|
|
case 281: // 3.7.1.0[BattyRope9]
|
|
case 288: // 3.7.1.3[BattyRope9]
|
|
case 295: // 3.7.2.1[BattyRope9]
|
|
case 302: // 3.7.2.4[BattyRope9]
|
|
case 309: // 3.7.2.8[BattyRope9]
|
|
case 316: // 3.7.2.15[BattyRope9]
|
|
case 323: // 3.7.2.20[BattyRope9]
|
|
case 330: // 3.7.2.24[BattyRope9]
|
|
case 337: // 3.7.2.25[BattyRope9]
|
|
case 344: // 3.7.2.26[BattyRope9]
|
|
case 351: // 3.7.2.27[BattyRope9]
|
|
case 358: // 3.7.2.29[BattyRope9]
|
|
case 365: // 3.7.2.31[BattyRope9]
|
|
case 372: // 3.7.2.32[BattyRope9]
|
|
case 379: // 3.7.2.33[BattyRope9]
|
|
case 386: // 3.7.2.34[BattyRope9]
|
|
case 393: // 3.7.2.36[BattyRope9]
|
|
case 400: // 3.7.2.40[BattyRope9]
|
|
case 407: // 3.7.2.42[BattyRope9]
|
|
case 414: // 3.7.2.44[BattyRope9]
|
|
case 421: // 3.7.2.45[BattyRope9]
|
|
case 428: // 3.7.2.73[BattyRope9]
|
|
case 435: // 3.7.2.74[BattyRope9]
|
|
case 442: // 3.7.2.78[BattyRope9]
|
|
case 449: // 3.7.2.82[BattyRope9]
|
|
case 456: // 3.7.2.90[BattyRope9]
|
|
case 463: // 3.7.2.91[BattyRope9]
|
|
case 470: // 3.7.2.92[BattyRope9]
|
|
case 477: // 3.7.2.95[BattyRope9]
|
|
setBattyRope3And5To9();
|
|
break;
|
|
case 25: // 3.6.19.16[150% speed]
|
|
case 27: // 3.6.19.17[150% speed]
|
|
case 64: // 3.6.27.9[150% speed]
|
|
case 74: // 3.6.28.0[150% speed]
|
|
case 87: // 3.6.29.3[150% speed]
|
|
case 97: // 3.6.29.20[150% speed]
|
|
case 103: // 3.6.29.21[150% speed]
|
|
case 109: // 3.6.29.22[150% speed]
|
|
case 116: // 3.6.29.23[150% speed]
|
|
case 123: // 3.6.29.24[150% speed]
|
|
case 139: // 3.6.29.47[150% speed]
|
|
case 146: // 3.6.29.49[150% speed]
|
|
case 155: // 3.6.30.0[150% speed]
|
|
case 163: // 3.6.31.0[150% speed]
|
|
case 184: // 3.6.31.3[150% speed]
|
|
case 191: // 3.6.31.5[150% speed]
|
|
case 198: // 3.6.31.7[150% speed]
|
|
case 205: // 3.6.31.12[150% speed]
|
|
case 212: // 3.6.31.14[150% speed]
|
|
case 219: // 3.6.31.22[150% speed]
|
|
case 226: // 3.6.31.23[150% speed]
|
|
case 233: // 3.6.31.31[150% speed]
|
|
case 240: // 3.7.0.0 RC1-3[150% speed]
|
|
case 247: // 3.7.0.0[150% speed]
|
|
case 254: // 3.7.0.2[150% speed]
|
|
case 261: // 3.7.0.3[150% speed]
|
|
case 268: // 3.7.0.4[150% speed]
|
|
case 275: // 3.7.0.5[150% speed]
|
|
case 282: // 3.7.1.0[150% speed]
|
|
case 289: // 3.7.1.3[150% speed]
|
|
case 296: // 3.7.2.1[150% speed]
|
|
case 303: // 3.7.2.4[150% speed]
|
|
case 310: // 3.7.2.8[150% speed]
|
|
case 317: // 3.7.2.15[150% speed]
|
|
case 324: // 3.7.2.20[150% speed]
|
|
case 331: // 3.7.2.24[150% speed]
|
|
case 338: // 3.7.2.25[150% speed]
|
|
case 345: // 3.7.2.26[150% speed]
|
|
case 352: // 3.7.2.27[150% speed]
|
|
case 359: // 3.7.2.29[150% speed]
|
|
case 366: // 3.7.2.31[150% speed]
|
|
case 373: // 3.7.2.32[150% speed]
|
|
case 380: // 3.7.2.33[150% speed]
|
|
case 387: // 3.7.2.34[150% speed]
|
|
case 394: // 3.7.2.36[150% speed]
|
|
case 401: // 3.7.2.40[150% speed]
|
|
case 408: // 3.7.2.42[150% speed]
|
|
case 415: // 3.7.2.44[150% speed]
|
|
case 422: // 3.7.2.45[150% speed]
|
|
case 429: // 3.7.2.73[150% speed]
|
|
case 436: // 3.7.2.74[150% speed]
|
|
case 443: // 3.7.2.78[150% speed]
|
|
case 450: // 3.7.2.82[150% speed]
|
|
case 457: // 3.7.2.90[150% speed]
|
|
case 464: // 3.7.2.91[150% speed]
|
|
case 471: // 3.7.2.92[150% speed]
|
|
case 478: // 3.7.2.95[150% speed]
|
|
set150Speed();
|
|
break;
|
|
case 39: // 3.6.24.1[TestStuff1]
|
|
setTestStuff1();
|
|
break;
|
|
case 40: // 3.6.24.1[TestStuff2]
|
|
setTestStuff2();
|
|
break;
|
|
case 46: // 3.6.26.4c[TestStuff3]
|
|
case 65: // 3.6.27.9[TestStuff3]
|
|
setTestStuff3();
|
|
break;
|
|
case 69: // 3.6.27.11[TestStuff4]
|
|
setTestStuff4();
|
|
break;
|
|
case 75: // 3.6.28.0[TestStuff5]
|
|
setTestStuff5();
|
|
break;
|
|
case 88: // 3.6.29.3[TestStuff6]
|
|
case 98: // 3.6.29.20[TestStuff6]
|
|
case 104: // 3.6.29.21[TestStuff6]
|
|
case 110: // 3.6.29.22[TestStuff7]
|
|
case 117: // 3.6.29.23[TestStuff7]
|
|
case 124: // 3.6.29.24[TestStuff7]
|
|
case 140: // 3.6.29.47[TestStuff8]
|
|
case 147: // 3.6.29.49[TestStuff8]
|
|
case 156: // 3.6.30.0[TestStuff8]
|
|
case 164: // 3.6.31.0[TestStuff8]
|
|
case 185: // 3.6.31.3[TestStuff8]
|
|
case 192: // 3.6.31.5[TestStuff8]
|
|
case 199: // 3.6.31.7[TestStuff8]
|
|
case 206: // 3.6.31.12[TestStuff8]
|
|
case 213: // 3.6.31.14[TestStuff8]
|
|
case 220: // 3.6.31.22[TestStuff8]
|
|
case 227: // 3.6.31.23[TestStuff8]
|
|
case 234: // 3.6.31.31[TestStuff9]
|
|
case 241: // 3.7.0.0 RC1-3[TestStuff9]
|
|
case 248: // 3.7.0.0[TestStuff9]
|
|
case 255: // 3.7.0.2[TestStuff9]
|
|
case 262: // 3.7.0.3[TestStuff9]
|
|
case 269: // 3.7.0.4[TestStuff9]
|
|
case 276: // 3.7.0.5[TestStuff9]
|
|
case 283: // 3.7.1.0[TestStuff9]
|
|
case 290: // 3.7.1.3[TestStuff9]
|
|
case 297: // 3.7.2.1[TestStuff9]
|
|
case 304: // 3.7.2.4[TestStuff9]
|
|
case 311: // 3.7.2.8[TestStuff9]
|
|
case 318: // 3.7.2.15[TestStuff9]
|
|
case 325: // 3.7.2.20[TestStuff9]
|
|
case 332: // 3.7.2.24[TestStuff9]
|
|
case 339: // 3.7.2.25[TestStuff9]
|
|
case 346: // 3.7.2.26[TestStuff9]
|
|
case 353: // 3.7.2.27[TestStuff9]
|
|
case 360: // 3.7.2.29[TestStuff9]
|
|
case 367: // 3.7.2.31[TestStuff9]
|
|
case 374: // 3.7.2.32[TestStuff9]
|
|
case 381: // 3.7.2.33[TestStuff9]
|
|
case 388: // 3.7.2.34[TestStuff9]
|
|
case 395: // 3.7.2.36[TestStuff9]
|
|
case 402: // 3.7.2.40[TestStuff9]
|
|
case 409: // 3.7.2.42[TestStuff9]
|
|
case 416: // 3.7.2.44[TestStuff9]
|
|
case 423: // 3.7.2.45[TestStuff9]
|
|
case 430: // 3.7.2.73[TestStuff9]
|
|
case 437: // 3.7.2.74[TestStuff9]
|
|
case 444: // 3.7.2.78[TestStuff9]
|
|
case 451: // 3.7.2.82[TestStuff9]
|
|
case 458: // 3.7.2.90[TestStuff9]
|
|
case 465: // 3.7.2.91[TestStuff9]
|
|
case 472: // 3.7.2.92[TestStuff9]
|
|
case 479: // 3.7.2.95[TestStuff9]
|
|
setTestStuff6To9();
|
|
break;
|
|
case 99: // 3.6.29.20[RacingStuff-pre]
|
|
setRacingStuffPre();
|
|
break;
|
|
case 105: // 3.6.29.21[RacingStuff]
|
|
case 111: // 3.6.29.22[RacingStuff]
|
|
case 118: // 3.6.29.23[RacingStuff]
|
|
case 125: // 3.6.29.24[RacingStuff]
|
|
case 141: // 3.6.29.47[RacingStuff]
|
|
case 148: // 3.6.29.49[RacingStuff]
|
|
case 157: // 3.6.30.0[RacingStuff]
|
|
case 165: // 3.6.31.0[RacingStuff]
|
|
case 186: // 3.6.31.3[RacingStuff]
|
|
case 193: // 3.6.31.5[RacingStuff]
|
|
case 200: // 3.6.31.7[RacingStuff]
|
|
case 207: // 3.6.31.12[RacingStuff]
|
|
case 214: // 3.6.31.14[RacingStuff]
|
|
case 221: // 3.6.31.22[RacingStuff]
|
|
case 228: // 3.6.31.23[RacingStuff]
|
|
case 235: // 3.6.31.31[RacingStuff]
|
|
case 242: // 3.7.0.0 RC1-3[RacingStuff]
|
|
case 249: // 3.7.0.0[RacingStuff]
|
|
case 256: // 3.7.0.2[RacingStuff]
|
|
case 263: // 3.7.0.3[RacingStuff]
|
|
case 270: // 3.7.0.4[RacingStuff]
|
|
case 277: // 3.7.0.5[RacingStuff]
|
|
case 284: // 3.7.1.0[RacingStuff]
|
|
case 291: // 3.7.1.3[RacingStuff]
|
|
case 298: // 3.7.2.1[RacingStuff]
|
|
case 305: // 3.7.2.4[RacingStuff]
|
|
case 312: // 3.7.2.8[RacingStuff]
|
|
case 319: // 3.7.2.15[RacingStuff]
|
|
case 326: // 3.7.2.20[RacingStuff]
|
|
case 333: // 3.7.2.24[RacingStuff]
|
|
case 340: // 3.7.2.25[RacingStuff]
|
|
case 347: // 3.7.2.26[RacingStuff]
|
|
case 354: // 3.7.2.27[RacingStuff]
|
|
case 361: // 3.7.2.29[RacingStuff]
|
|
case 368: // 3.7.2.31[RacingStuff]
|
|
case 375: // 3.7.2.32[RacingStuff]
|
|
case 382: // 3.7.2.33[RacingStuff]
|
|
case 389: // 3.7.2.34[RacingStuff]
|
|
case 396: // 3.7.2.36[RacingStuff]
|
|
case 403: // 3.7.2.40[RacingStuff]
|
|
case 410: // 3.7.2.42[RacingStuff]
|
|
case 417: // 3.7.2.44[RacingStuff]
|
|
case 424: // 3.7.2.45[RacingStuff]
|
|
case 431: // 3.7.2.73[RacingStuff]
|
|
case 438: // 3.7.2.74[RacingStuff]
|
|
case 445: // 3.7.2.78[RacingStuff]
|
|
case 452: // 3.7.2.82[RacingStuff]
|
|
case 459: // 3.7.2.90[RacingStuff]
|
|
case 466: // 3.7.2.91[RacingStuff]
|
|
case 473: // 3.7.2.92[RacingStuff]
|
|
case 480: // 3.7.2.95[RacingStuff]
|
|
setRacingStuff();
|
|
break;
|
|
case 112: // 3.6.29.22[BoomRacing]
|
|
case 119: // 3.6.29.23[BoomRacing]
|
|
case 126: // 3.6.29.24[BoomRacing]
|
|
case 142: // 3.6.29.47[BoomRacing]
|
|
case 149: // 3.6.29.49[BoomRacing]
|
|
case 158: // 3.6.30.0[BoomRacing]
|
|
case 166: // 3.6.31.0[BoomRacing]
|
|
case 187: // 3.6.31.3[BoomRacing]
|
|
case 194: // 3.6.31.5[BoomRacing]
|
|
case 201: // 3.6.31.7[BoomRacing]
|
|
case 208: // 3.6.31.12[BoomRacing]
|
|
case 215: // 3.6.31.14[BoomRacing]
|
|
case 222: // 3.6.31.22[BoomRacing]
|
|
case 229: // 3.6.31.23[BoomRacing]
|
|
case 236: // 3.6.31.31[BoomRacing]
|
|
case 243: // 3.7.0.0 RC1-3[BoomRacing]
|
|
case 250: // 3.7.0.0[BoomRacing]
|
|
case 257: // 3.7.0.2[BoomRacing]
|
|
case 264: // 3.7.0.3[BoomRacing]
|
|
case 271: // 3.7.0.4[BoomRacing]
|
|
case 278: // 3.7.0.5[BoomRacing]
|
|
case 285: // 3.7.1.0[BoomRacing]
|
|
case 292: // 3.7.1.3[BoomRacing]
|
|
case 299: // 3.7.2.1[BoomRacing]
|
|
case 306: // 3.7.2.4[BoomRacing]
|
|
case 313: // 3.7.2.8[BoomRacing]
|
|
case 320: // 3.7.2.15[BoomRacing]
|
|
case 327: // 3.7.2.20[BoomRacing]
|
|
case 334: // 3.7.2.24[BoomRacing]
|
|
case 341: // 3.7.2.25[BoomRacing]
|
|
case 348: // 3.7.2.26[BoomRacing]
|
|
case 355: // 3.7.2.27[BoomRacing]
|
|
case 362: // 3.7.2.29[BoomRacing]
|
|
case 369: // 3.7.2.31[BoomRacing]
|
|
case 376: // 3.7.2.32[BoomRacing]
|
|
case 383: // 3.7.2.33[BoomRacing]
|
|
case 390: // 3.7.2.34[BoomRacing]
|
|
case 397: // 3.7.2.36[BoomRacing]
|
|
case 404: // 3.7.2.40[BoomRacing]
|
|
case 411: // 3.7.2.42[BoomRacing]
|
|
case 418: // 3.7.2.44[BoomRacing]
|
|
case 425: // 3.7.2.45[BoomRacing]
|
|
case 432: // 3.7.2.73[BoomRacing]
|
|
case 439: // 3.7.2.74[BoomRacing]
|
|
case 446: // 3.7.2.78[BoomRacing]
|
|
case 453: // 3.7.2.82[BoomRacing]
|
|
case 460: // 3.7.2.90[BoomRacing]
|
|
case 467: // 3.7.2.91[BoomRacing]
|
|
case 474: // 3.7.2.92[BoomRacing]
|
|
case 481: // 3.7.2.95[BoomRacing]
|
|
setBoomRacing();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
|
|
|
/// <inheritdoc/>
|
|
public override bool Equals(object? obj) => Equals(obj as Scheme);
|
|
|
|
/// <inheritdoc/>
|
|
public bool Equals(Scheme? other)
|
|
=> other != null
|
|
&& Version == other.Version
|
|
&& HotSeatTime == other.HotSeatTime
|
|
&& RetreatTime == other.RetreatTime
|
|
&& RetreatTimeRope == other.RetreatTimeRope
|
|
&& ShowRoundTime == other.ShowRoundTime
|
|
&& Replays == other.Replays
|
|
&& FallDamage == other.FallDamage
|
|
&& ArtilleryMode == other.ArtilleryMode
|
|
&& SchemeEditor == other.SchemeEditor
|
|
&& Stockpiling == other.Stockpiling
|
|
&& WormSelect == other.WormSelect
|
|
&& SuddenDeathEvent == other.SuddenDeathEvent
|
|
&& WaterRiseRate == other.WaterRiseRate
|
|
&& WeaponCrateProb == other.WeaponCrateProb
|
|
&& DonorCards == other.DonorCards
|
|
&& HealthCrateProb == other.HealthCrateProb
|
|
&& HealthCrateEnergy == other.HealthCrateEnergy
|
|
&& UtilityCrateProb == other.UtilityCrateProb
|
|
&& ObjectTypes == other.ObjectTypes
|
|
&& ObjectCount == other.ObjectCount
|
|
&& MineDelay == other.MineDelay
|
|
&& MineDelayRandom == other.MineDelayRandom
|
|
&& DudMines == other.DudMines
|
|
&& ManualWormPlacement == other.ManualWormPlacement
|
|
&& WormEnergy == other.WormEnergy
|
|
&& TurnTime == other.TurnTime
|
|
&& TurnTimeInfinite == other.TurnTimeInfinite
|
|
&& RoundTimeMinutes == other.RoundTimeMinutes
|
|
&& RoundTimeSeconds == other.RoundTimeSeconds
|
|
&& NumberOfWins == other.NumberOfWins
|
|
&& Blood == other.Blood
|
|
&& AquaSheep == other.AquaSheep
|
|
&& SheepHeaven == other.SheepHeaven
|
|
&& GodWorms == other.GodWorms
|
|
&& 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(other.Extended)
|
|
&& Attachment.SequenceEqual(other.Attachment)
|
|
&& RwVersion == other.RwVersion;
|
|
|
|
/// <inheritdoc/>
|
|
public override int GetHashCode()
|
|
{
|
|
HashCode hash = new HashCode();
|
|
hash.Add(Version);
|
|
hash.Add(HotSeatTime);
|
|
hash.Add(RetreatTime);
|
|
hash.Add(RetreatTimeRope);
|
|
hash.Add(ShowRoundTime);
|
|
hash.Add(Replays);
|
|
hash.Add(FallDamage);
|
|
hash.Add(ArtilleryMode);
|
|
hash.Add(SchemeEditor);
|
|
hash.Add(Stockpiling);
|
|
hash.Add(WormSelect);
|
|
hash.Add(SuddenDeathEvent);
|
|
hash.Add(WaterRiseRate);
|
|
hash.Add(WeaponCrateProb);
|
|
hash.Add(DonorCards);
|
|
hash.Add(HealthCrateProb);
|
|
hash.Add(HealthCrateEnergy);
|
|
hash.Add(UtilityCrateProb);
|
|
hash.Add(ObjectTypes);
|
|
hash.Add(ObjectCount);
|
|
hash.Add(MineDelay);
|
|
hash.Add(MineDelayRandom);
|
|
hash.Add(DudMines);
|
|
hash.Add(ManualWormPlacement);
|
|
hash.Add(WormEnergy);
|
|
hash.Add(TurnTime);
|
|
hash.Add(TurnTimeInfinite);
|
|
hash.Add(RoundTimeMinutes);
|
|
hash.Add(RoundTimeSeconds);
|
|
hash.Add(NumberOfWins);
|
|
hash.Add(Blood);
|
|
hash.Add(AquaSheep);
|
|
hash.Add(SheepHeaven);
|
|
hash.Add(GodWorms);
|
|
hash.Add(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(RwVersion);
|
|
return hash.ToHashCode();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Load(Stream stream)
|
|
{
|
|
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
|
|
|
|
void readObjectCombo()
|
|
{
|
|
byte raw = reader.Read1Byte();
|
|
if (Version == SchemeVersion.Version1)
|
|
{
|
|
// Special handling for broken "v1" schemes setting illegal values here, bugging out old frontends.
|
|
ObjectCount = 8;
|
|
ObjectTypes = raw switch
|
|
{
|
|
0 => MapObjectType.None,
|
|
2 => MapObjectType.OilDrums,
|
|
5 => MapObjectType.Both,
|
|
_ => MapObjectType.Mines
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// Actual handling as implemented in 3.6.29.0 frontend.
|
|
switch (raw)
|
|
{
|
|
case 3: case 4: raw = 1; break;
|
|
case 6: case 7: raw = 0; break;
|
|
}
|
|
|
|
// Determine object count.
|
|
int step = Math.Min(raw < 8 ? 8 : raw / 4 - 2, _objectCountLastStep);
|
|
if (step >= _objectCount10Step)
|
|
step = 100 + 10 * (step - _objectCount10Step);
|
|
else if (step >= _objectCount5Step)
|
|
step = 30 + 5 * (step - _objectCount5Step);
|
|
ObjectCount = (byte)step;
|
|
|
|
// Determine object types.
|
|
ObjectTypes = raw == 5 // old "both" value
|
|
? MapObjectType.Both
|
|
: (MapObjectType)(raw & (byte)MapObjectType.Both);
|
|
}
|
|
}
|
|
|
|
void readMineDelay()
|
|
{
|
|
byte raw = reader.Read1Byte();
|
|
MineDelayRandom = raw == 4 || raw > 127;
|
|
MineDelay = MineDelayRandom ? (byte)0 : raw;
|
|
}
|
|
|
|
void readTurnTime()
|
|
{
|
|
byte raw = reader.Read1Byte();
|
|
TurnTimeInfinite = raw > 127;
|
|
TurnTime = TurnTimeInfinite ? (byte)0 : raw;
|
|
}
|
|
|
|
void readRoundTime()
|
|
{
|
|
byte raw = reader.Read1Byte();
|
|
if (raw > 127)
|
|
{
|
|
RoundTimeMinutes = 0;
|
|
RoundTimeSeconds = (byte)(256 - raw);
|
|
}
|
|
else
|
|
{
|
|
RoundTimeMinutes = raw;
|
|
RoundTimeSeconds = 0;
|
|
}
|
|
}
|
|
|
|
// Read the header.
|
|
if (reader.ReadUInt32() != _signature)
|
|
throw new InvalidDataException("Invalid scheme file signature.");
|
|
Version = reader.ReadEnum<SchemeVersion>(true);
|
|
|
|
// Read the options.
|
|
HotSeatTime = reader.Read1Byte();
|
|
RetreatTime = reader.Read1Byte();
|
|
RetreatTimeRope = reader.Read1Byte();
|
|
ShowRoundTime = reader.ReadBoolean();
|
|
Replays = reader.ReadBoolean();
|
|
FallDamage = reader.ReadByte() * 50 % 256 * 2;
|
|
ArtilleryMode = reader.ReadBoolean();
|
|
SchemeEditor = reader.ReadEnum<SchemeEditor>(false);
|
|
Stockpiling = reader.ReadEnum<Stockpiling>(true);
|
|
WormSelect = reader.ReadEnum<WormSelect>(true);
|
|
SuddenDeathEvent = reader.ReadEnum<SuddenDeathEvent>(true);
|
|
_waterRiseIndex = reader.Read1Byte();
|
|
WeaponCrateProb = reader.ReadSByte();
|
|
DonorCards = reader.ReadBoolean();
|
|
HealthCrateProb = reader.ReadSByte();
|
|
HealthCrateEnergy = reader.Read1Byte();
|
|
UtilityCrateProb = reader.ReadSByte();
|
|
readObjectCombo();
|
|
readMineDelay();
|
|
DudMines = reader.ReadBoolean();
|
|
ManualWormPlacement = reader.ReadBoolean();
|
|
WormEnergy = reader.Read1Byte();
|
|
readTurnTime();
|
|
readRoundTime();
|
|
NumberOfWins = reader.Read1Byte();
|
|
Blood = reader.ReadBoolean();
|
|
AquaSheep = reader.ReadBoolean();
|
|
SheepHeaven = reader.ReadBoolean();
|
|
GodWorms = 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();
|
|
reader.ReadStructs(Weapons.AsSpan(Version.GetWeaponCount()));
|
|
|
|
// Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities.
|
|
switch (Version)
|
|
{
|
|
case SchemeVersion.Version2: LoadRubberWorm(); break;
|
|
case SchemeVersion.Version3: LoadExtendedOptions(reader); break;
|
|
}
|
|
|
|
// Read any trailing bytes (either unknown extended scheme options or unknown WWP trash).
|
|
Attachment = reader.EndOfStream
|
|
? Array.Empty<byte>()
|
|
: reader.ReadBytes((int)(reader.Length - reader.Position));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Load(string fileName)
|
|
{
|
|
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
Load(stream);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the data into the given <paramref name="stream"/> with a format appropriate for <see cref="Version"/>.
|
|
/// </summary>
|
|
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
|
|
public void Save(Stream stream) => Save(stream, (SchemeSaveFormat)((uint)Version << 16));
|
|
|
|
/// <summary>
|
|
/// Saves the data in the given file with a format appropriate for <see cref="Version"/>.
|
|
/// </summary>
|
|
/// <param name="fileName">The name of the file to save the data in.</param>
|
|
public void Save(string fileName) => Save(fileName, (SchemeSaveFormat)((uint)Version << 16));
|
|
|
|
/// <summary>
|
|
/// Saves the data in the given file with the specified <paramref name="format"/>.
|
|
/// </summary>
|
|
/// <param name="fileName">The name of the file to save the data in.</param>
|
|
/// <param name="format">The <see cref="SchemeSaveFormat"/> to respect when storing the settings.</param>
|
|
public void Save(string fileName, SchemeSaveFormat format)
|
|
{
|
|
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
|
|
Save(stream, format);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the data into the given <paramref name="stream"/> with the specified <paramref name="format"/>.
|
|
/// </summary>
|
|
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
|
|
/// <param name="format">The <see cref="SchemeSaveFormat"/> to respect when storing the settings.</param>
|
|
public void Save(Stream stream, SchemeSaveFormat format)
|
|
{
|
|
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
|
|
|
|
void saveObjectTypesAndCount()
|
|
{
|
|
byte raw;
|
|
if ((format == SchemeSaveFormat.Version2ObjectCount || format >= SchemeSaveFormat.Version3)
|
|
&& ObjectCount != 8)
|
|
{
|
|
// WA since 3.6.28.0 encodes object type and count in one byte.
|
|
raw = (byte)ObjectTypes;
|
|
|
|
int step = ObjectCount;
|
|
if (ObjectCount >= 100)
|
|
step = _objectCount10Step + (ObjectCount - 100) / 10;
|
|
else if (ObjectCount >= 30)
|
|
step = _objectCount5Step + (ObjectCount - 30) / 5;
|
|
raw |= (byte)(4 * step + 8);
|
|
}
|
|
else
|
|
{
|
|
// WA before 3.6.28.0 and WWP only store object type.
|
|
raw = ObjectTypes == MapObjectType.Both ? (byte)5 : (byte)ObjectTypes;
|
|
}
|
|
writer.Write(raw);
|
|
}
|
|
|
|
void saveMineDelayConfig() => writer.Write(MineDelayRandom ? (byte)4 : MineDelay);
|
|
|
|
void saveTurnTimeConfig() => writer.Write(TurnTimeInfinite ? (byte)255 : TurnTime);
|
|
|
|
void saveRoundTimeConfig() => writer.Write(RoundTimeSeconds > 0
|
|
? (byte)(256 - RoundTimeSeconds)
|
|
: RoundTimeMinutes);
|
|
|
|
// Write the header.
|
|
SchemeVersion version = (SchemeVersion)((uint)format >> 16);
|
|
writer.Write(_signature);
|
|
writer.WriteEnum(version);
|
|
|
|
// Write the options.
|
|
writer.Write(HotSeatTime);
|
|
writer.Write(RetreatTime);
|
|
writer.Write(RetreatTimeRope);
|
|
writer.Write(ShowRoundTime);
|
|
writer.Write(Replays);
|
|
writer.Write((byte)(FallDamage / 4 * 41 % 128));
|
|
writer.Write(ArtilleryMode);
|
|
writer.WriteEnum(SchemeEditor, false);
|
|
writer.WriteEnum(Stockpiling, true);
|
|
writer.WriteEnum(WormSelect, true);
|
|
writer.WriteEnum(SuddenDeathEvent, true);
|
|
writer.Write(_waterRiseIndex);
|
|
writer.Write(WeaponCrateProb);
|
|
writer.Write(DonorCards);
|
|
writer.Write(HealthCrateProb);
|
|
writer.Write(HealthCrateEnergy);
|
|
writer.Write(UtilityCrateProb);
|
|
saveObjectTypesAndCount();
|
|
saveMineDelayConfig();
|
|
writer.Write(DudMines);
|
|
writer.Write(ManualWormPlacement);
|
|
writer.Write(WormEnergy);
|
|
saveTurnTimeConfig();
|
|
saveRoundTimeConfig();
|
|
writer.Write(NumberOfWins);
|
|
writer.Write(Blood);
|
|
writer.Write(AquaSheep);
|
|
writer.Write(SheepHeaven);
|
|
writer.Write(GodWorms);
|
|
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.
|
|
writer.WriteStructs<SchemeWeapon>(Weapons.AsSpan(version.GetWeaponCount()));
|
|
|
|
// Clear RubberWorm probabilities again or write available extended settings.
|
|
switch (version)
|
|
{
|
|
case SchemeVersion.Version2: ClearRubberWorm(); break;
|
|
case SchemeVersion.Version3: SaveExtendedOptions(writer, Attachment.Length == 0); break;
|
|
}
|
|
|
|
// Write any trailing bytes (either unknown extended scheme options or unknown WWP trash).
|
|
writer.Write(Attachment);
|
|
}
|
|
|
|
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
|
|
|
private void ClearRubberWorm()
|
|
{
|
|
// Reset all super weapon probabilities to remove any settings made by RubberWorm.
|
|
for (Weapon superWeapon = Weapon.Freeze; superWeapon <= Weapon.Armageddon; superWeapon++)
|
|
Weapons[superWeapon].Prob = 0;
|
|
}
|
|
|
|
private void LoadExtendedOptions(BinaryStream reader)
|
|
{
|
|
// Create a copy of default options overwritten by available extended data.
|
|
Span<byte> bytes = Extended.AsSpan();
|
|
ExtendedOptions.Default.AsSpan().CopyTo(bytes);
|
|
reader.Read(bytes);
|
|
}
|
|
|
|
private void LoadRubberWorm()
|
|
{
|
|
unchecked
|
|
{
|
|
// Earthquake flags.
|
|
byte prob = (byte)Weapons[Weapon.Earthquake].Prob;
|
|
Extended.AutoReaim = prob.GetBit(0);
|
|
Extended.CircularAim = prob.GetBit(1);
|
|
Extended.AntiLockPower = prob.GetBit(2);
|
|
Extended.ShotDoesntEndTurnAll = prob.GetBit(3);
|
|
Extended.KaosMod = prob.DecodeByte(4, 4);
|
|
|
|
// Antisink.
|
|
Extended.AntiSink = Weapons[Weapon.SheepStrike].Prob != 0;
|
|
|
|
// Crate limit and rate.
|
|
prob = (byte)Weapons[Weapon.MagicBullet].Prob;
|
|
Extended.CrateLimit = prob switch
|
|
{
|
|
0 => ExtendedOptions.Default.CrateLimit,
|
|
_ => prob
|
|
};
|
|
Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Prob;
|
|
|
|
// Mole squadron flags.
|
|
prob = (byte)Weapons[Weapon.MoleSquadron].Prob;
|
|
Extended.ShotDoesntEndTurn = prob.GetBit(0);
|
|
Extended.LoseControlDoesntEndTurn = prob.GetBit(1);
|
|
Extended.FiringPausesTimer = !prob.GetBit(2);
|
|
Extended.RopeUpgrade = prob.GetBit(3);
|
|
Extended.CrateShower = prob.GetBit(4);
|
|
Extended.WeaponsDontChange = prob.GetBit(6);
|
|
Extended.ObjectPushByExplosion = prob.GetBit(5) ? (bool?)true : null;
|
|
Extended.ExtendedFuse = prob.GetBit(7);
|
|
|
|
// Flame limit.
|
|
prob = (byte)Weapons[Weapon.ScalesOfJustice].Prob;
|
|
if (prob > 0)
|
|
Extended.FlameLimit = (ushort)(prob * 100);
|
|
|
|
// Friction.
|
|
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].Prob;
|
|
if (prob == 0)
|
|
{
|
|
Extended.RwGravityType = RwGravityType.None;
|
|
Extended.RwGravity = ExtendedOptions.Default.RwGravity;
|
|
}
|
|
else if (prob.GetBit(7) && prob.GetBit(6))
|
|
{
|
|
Extended.RwGravityType = RwGravityType.BlackHoleLinear;
|
|
Extended.RwGravity = prob.DecodeSByte(6) * 512;
|
|
}
|
|
else if (prob.GetBit(7))
|
|
{
|
|
Extended.RwGravityType = RwGravityType.BlackHoleConstant;
|
|
Extended.RwGravity = prob.DecodeSByte(6) * 512;
|
|
}
|
|
else
|
|
{
|
|
Extended.RwGravityType = RwGravityType.Default;
|
|
Extended.RwGravity = prob.DecodeSByte(7) * 512;
|
|
}
|
|
|
|
// Rope-knocking force.
|
|
prob = (byte)Weapons[Weapon.SuperBananaBomb].Prob;
|
|
Extended.RopeKnockForce = prob switch
|
|
{
|
|
0 => null,
|
|
Byte.MaxValue => 0,
|
|
_ => prob
|
|
};
|
|
|
|
// Maximum rope speed.
|
|
prob = (byte)Weapons[Weapon.MineStrike].Prob;
|
|
Extended.RopeMaxSpeed = prob switch
|
|
{
|
|
0 => ExtendedOptions.Default.RopeMaxSpeed,
|
|
Byte.MaxValue => 0,
|
|
_ => prob
|
|
};
|
|
|
|
// Select worm any time.
|
|
Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Prob).GetBit(0);
|
|
|
|
// Viscosity.
|
|
prob = (byte)Weapons[Weapon.ConcreteDonkey].Prob;
|
|
Extended.Viscosity = prob / 255f;
|
|
Extended.ViscosityWorms = (prob & 1) == 1;
|
|
|
|
// Wind.
|
|
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].Prob / 255f;
|
|
|
|
// Version override.
|
|
RwVersion = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[]
|
|
{
|
|
(byte)Weapons[Weapon.SelectWorm].Prob,
|
|
(byte)Weapons[Weapon.Freeze].Prob
|
|
});
|
|
}
|
|
|
|
// Clear abused probabilities.
|
|
ClearRubberWorm();
|
|
}
|
|
|
|
private void SaveExtendedOptions(BinaryStream writer, bool trim)
|
|
{
|
|
ReadOnlySpan<byte> bytes = Extended.AsSpan();
|
|
|
|
// Trim away default bytes.
|
|
int length = bytes.Length;
|
|
if (trim)
|
|
{
|
|
ReadOnlySpan<byte> defaultBytes = ExtendedOptions.Default.AsSpan();
|
|
for (; length > 0 && bytes[length - 1] == defaultBytes[length - 1]; length--) ;
|
|
}
|
|
|
|
writer.Write(bytes.Slice(0, length));
|
|
}
|
|
|
|
private void SaveRubberWorm()
|
|
{
|
|
unchecked
|
|
{
|
|
// Earthquake flags.
|
|
byte prob = 0;
|
|
prob = prob.SetBit(0, Extended.AutoReaim);
|
|
prob = prob.SetBit(1, Extended.CircularAim);
|
|
prob = prob.SetBit(2, Extended.AntiLockPower);
|
|
prob = prob.SetBit(3, Extended.ShotDoesntEndTurnAll);
|
|
prob = prob.Encode(Extended.KaosMod, 4, 4);
|
|
Weapons[Weapon.Earthquake].Prob = (sbyte)prob;
|
|
|
|
// Antisink
|
|
Weapons[Weapon.SheepStrike].Prob = (sbyte)(Extended.AntiSink ? 1 : 0);
|
|
|
|
// Crate limit and rate.
|
|
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;
|
|
prob = prob.SetBit(0, Extended.ShotDoesntEndTurn);
|
|
prob = prob.SetBit(1, Extended.LoseControlDoesntEndTurn);
|
|
prob = prob.SetBit(2, !Extended.FiringPausesTimer);
|
|
prob = prob.SetBit(3, Extended.RopeUpgrade);
|
|
prob = prob.SetBit(4, Extended.CrateShower);
|
|
prob = prob.SetBit(5, Extended.ObjectPushByExplosion == true);
|
|
prob = prob.SetBit(6, Extended.WeaponsDontChange);
|
|
prob = prob.SetBit(7, Extended.ExtendedFuse);
|
|
Weapons[Weapon.MoleSquadron].Prob = (sbyte)prob;
|
|
|
|
// Flame limit.
|
|
if (Extended.FlameLimit != ExtendedOptions.Default.FlameLimit)
|
|
Weapons[Weapon.ScalesOfJustice].Prob = (sbyte)(Extended.FlameLimit / 100);
|
|
|
|
// Friction.
|
|
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;
|
|
switch (Extended.RwGravityType)
|
|
{
|
|
case RwGravityType.BlackHoleConstant:
|
|
prob = prob.EnableBit(7);
|
|
prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6);
|
|
break;
|
|
case RwGravityType.BlackHoleLinear:
|
|
prob = prob.EnableBit(7);
|
|
prob = prob.EnableBit(6);
|
|
prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6);
|
|
break;
|
|
case RwGravityType.Default:
|
|
prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6);
|
|
break;
|
|
}
|
|
Weapons[Weapon.MailStrike].Prob = (sbyte)prob;
|
|
|
|
// Rope-knocking force.
|
|
Weapons[Weapon.SuperBananaBomb].Prob = (sbyte)(Extended.RopeKnockForce switch
|
|
{
|
|
null => 0,
|
|
0 => Byte.MaxValue,
|
|
_ => Extended.RopeKnockForce.Value
|
|
});
|
|
|
|
// Maximum rope speed.
|
|
Weapons[Weapon.MineStrike].Prob = (sbyte)(Extended.RopeMaxSpeed switch
|
|
{
|
|
16/*SchemeExtendedOptions.Default.RopeMaxSpeed*/ => 0,
|
|
0 => Byte.MaxValue,
|
|
_ => Math.Min(254, Extended.RopeMaxSpeed)
|
|
});
|
|
|
|
// Select worm any time.
|
|
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].Prob = (sbyte)prob;
|
|
|
|
// Wind.
|
|
prob = (byte)Math.Round(Extended.RwWind * 255, 0);
|
|
prob.SetBit(0, Extended.RwWindWorms);
|
|
Weapons[Weapon.SuicideBomber].Prob = (sbyte)prob;
|
|
|
|
// Worm bounce.
|
|
Weapons[Weapon.Armageddon].Prob = (sbyte)Math.Round(Extended.WormBounce * 255, 0);
|
|
|
|
// Version override.
|
|
Span<byte> versionBytes = stackalloc byte[sizeof(ushort)];
|
|
BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersion);
|
|
Weapons[Weapon.SelectWorm].Prob = (sbyte)versionBytes[0];
|
|
Weapons[Weapon.Freeze].Prob = (sbyte)versionBytes[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents the possible variations of the options stored in a scheme file.
|
|
/// </summary>
|
|
public enum SchemeSaveFormat
|
|
{
|
|
// Values are organized as follows:
|
|
// Scheme.Version << 16 = latest SchemeSaveFormat of it
|
|
// SchemeSaveFormat >> 16 = corresponding Scheme.Version
|
|
|
|
/// <summary>Represents <see cref="SchemeVersion.Version1"/>.</summary>
|
|
Version1 = 0x00010000,
|
|
/// <summary>Represents <see cref="SchemeVersion.Version2"/>.</summary>
|
|
Version2 = 0x00020001,
|
|
/// <summary>Represents <see cref="SchemeVersion.Version2"/>, storing object count since WA 3.6.28.0.</summary>
|
|
Version2ObjectCount = 0x00020000,
|
|
/// <summary>Represents <see cref="SchemeVersion.Version3"/>.</summary>
|
|
Version3 = 0x00030000
|
|
}
|
|
}
|