using System; using System.Collections.Generic; using System.IO; using System.Text; using Syroot.BinaryData; using Syroot.Worms.Core; using Syroot.Worms.Core.IO; namespace Syroot.Worms.Armageddon { /// /// 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. /// public class Scheme : ILoadableFile, ISaveableFile { // ---- CONSTANTS ---------------------------------------------------------------------------------------------- private const string _signature = "SCHM"; // Lookup tables. private static readonly Dictionary _mapWaterRiseToRaw = new Dictionary() { [0] = 0, [5] = 1, [13] = 19, [20] = 2, [21] = 55, [29] = 43, [37] = 47, [45] = 3, [52] = 26, [53] = 25, [61] = 27, [64] = 8, [69] = 33, [77] = 13, [80] = 4, [84] = 18, [85] = 23, [93] = 11, [101] = 15, [109] = 29, [116] = 22, [117] = 57, [125] = 5, [133] = 63, [141] = 45, [148] = 30, [149] = 9, [157] = 21, [165] = 17, [173] = 61, [180] = 6, [181] = 39, [189] = 37, [197] = 31, [205] = 51, [208] = 12, [212] = 14, [213] = 41, [221] = 53, [229] = 49, [237] = 35, [244] = 10, [245] = 7, [253] = 59 }; private static readonly byte[] _objectCounts = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250 }; // ---- MEMBERS ------------------------------------------------------------------------------------------------ private byte _mineDelay; private byte _turnTime; private byte _roundTimeMinutes; private byte _roundTimeSeconds; private byte _numberOfWins; private sbyte _rwGravity; private sbyte _rwGravityConstBlackHole; private sbyte _rwGravityPropBlackHole; private byte _rwKaosMod; // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ /// /// Initializes a new instance of the class. /// public Scheme() { Version = SchemeVersion.Extended; Weapons = new SchemeWeaponSetting[GetWeaponCount()]; } /// /// Initializes a new instance of the class, loading the data from the given /// . /// /// The to load the data from. public Scheme(Stream stream) { Load(stream); } /// /// Initializes a new instance of the class, loading the data from the given file. /// /// The name of the file to load the data from. public Scheme(string fileName) { Load(fileName); } // ---- PROPERTIES --------------------------------------------------------------------------------------------- /// /// Gets or sets the of this scheme, controlling whether super weapon settings /// are stored or not. /// public SchemeVersion Version { get; set; } /// /// Gets or sets the delay in seconds between each team's turn to allow relaxed switching of seats. /// public byte HotSeatDelay { get; set; } /// /// 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. /// public byte RetreatTime { get; set; } /// /// 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. /// public byte RetreatTimeRope { get; set; } /// /// Gets or sets a value indicating whether the total round time until sudden death will be displayed in the /// turn timer. /// public bool ShowRoundTime { get; set; } /// /// Gets or sets a value indicating whether significant turns will be replayed in offline games. /// public bool AutomaticReplays { get; set; } /// /// Gets or sets the percentual amount of fall damage applied, relative to normal fall damage being 100%. /// public SchemeFallDamage FallDamage { get; set; } /// /// Gets or sets a value indicating whether worms cannot walk and are mostly stuck at their current position /// without using any utilities. /// public bool ArtilleryMode { get; set; } /// /// Gets or sets a value indicating the scheme editor used to modify this scheme. Originally used to indicate /// the unimplemented Bounty Mode. /// public SchemeEditor SchemeEditor { get; set; } /// /// Gets or sets a value indicating the stockpiling of armory between game rounds. /// public SchemeStockpiling StockpilingMode { get; set; } /// /// Gets or sets a value indicating the worm selection order determining the next worm to be played. /// public SchemeWormSelect WormSelectMode { get; set; } /// /// Gets or sets a value indicating the action triggered when Sudden Death starts. /// public SchemeSuddenDeathEvent SuddenDeathEvent { get; set; } /// /// Gets or sets the amount in pixels which the water will rise between turns after Sudden Death was triggered. /// public SchemeWaterRise WaterRiseRate { get; set; } /// /// Gets or sets the percentual probability of a weapon crate to drop between turns. Negative values might crash /// the game. /// public sbyte WeaponCrateProbability { get; set; } /// /// Gets or sets a value indicating whether donor cards can spawn upon a worm's death. /// public bool DonorCards { get; set; } /// /// Gets or sets the percentual probability of a health crate to drop between turns. Negative values might crash /// the game. /// public sbyte HealthCrateProbability { get; set; } /// /// Gets or sets the amount of health included in a health crate added to the collecting worm's energy. /// public byte HealthCrateEnergy { get; set; } /// /// Gets or sets the percentual probability of a utility crate to drop between turns. Negative values might /// crash the game. /// public sbyte UtilityCrateProbability { get; set; } /// /// Gets or sets the type of objects which can be placed on the map. /// public SchemeObjectType ObjectTypes { get; set; } /// /// Gets or sets the maximum number of objects (mines or oil drums) on the map. /// public SchemeObjectCount ObjectCount { get; set; } /// /// Gets or sets the number of seconds a mine requires to explode. Can be 1-3 and 5-127 seconds. /// public byte MineDelay { get { return _mineDelay; } set { if (value == 4 || value > 0x7F) { throw new ArgumentException("Mine delay must be between 0-127 and not be 4.", nameof(value)); } _mineDelay = value; } } /// /// Gets or sets a value indicating whether the mine fuse will be randomly chosen between fractions of 1 to 3 /// seconds. If true, the setting will be ignored. /// public bool MineDelayRandom { get; set; } /// /// Gets or sets a value indicating whether mines can refuse to explode after their count down. /// public bool DudMines { get; set; } /// /// Gets or sets a value indicating whether worms are placed manually by the players for their initial position /// at round start. /// public bool ManualWormPlacement { get; set; } /// /// Gets or sets the initial worm energy at round start. /// public byte WormEnergy { get; set; } /// /// Gets or sets the turn time in seconds available for the player to move. Must be in the range of 0-127. /// public byte TurnTime { get { return _turnTime; } set { if (value > 0x7F) { throw new ArgumentException("Turn time must be between 0-127.", nameof(value)); } _turnTime = value; } } /// /// Gets or sets a value indicating whether the turn time is unlimited. If true, the /// setting will be ignored. /// public bool TurnTimeInfinite { get; set; } /// /// Gets or sets the round time before sudden death is triggered between 0-127 minutes. /// public byte RoundTimeMinutes { get { return _roundTimeMinutes; } set { if (value > 0x7F) { throw new ArgumentException("Round time must be between 0-127 minutes.", nameof(value)); } _roundTimeMinutes = value; } } /// /// Gets or sets the round time before sudden death is triggered in between 0-128 seconds. When 0, /// is used instead. /// public byte RoundTimeSeconds { get { return _roundTimeSeconds; } set { if (value > 0x80) { throw new ArgumentException("Round time must be between 0-128 seconds.", nameof(value)); } _roundTimeSeconds = value; } } /// /// Gets or sets the number of round wins required to win the game. Must not be 0. /// public byte NumberOfWins { get { return _numberOfWins; } set { if (value == 0) { throw new ArgumentException("Number of wins must not be 0.", nameof(value)); } _numberOfWins = value; } } /// /// Gets or sets a value indicating whether blood effects are enabled. /// public bool Blood { get; set; } /// /// Gets or sets a value indicating whether the Super Sheep weapon gets upgraded to the Aqua Sheep, which can /// fly underwater. /// public bool AquaSheep { get; set; } /// /// Gets or sets a value indicating whether sheeps will jump out of exploding crates. /// public bool SheepHeaven { get; set; } /// /// Gets or sets a value indicating whether worms have infinity energy, killable only by drowning them. /// public bool GodWorms { get; set; } /// /// Gets or sets a value indicating whether terrain cannot be destroyed by explosions. /// public bool IndestructibleLand { get; set; } /// /// Gets or sets a value indicating whether the Grenade weapon is more powerful. /// public bool UpgradedGrenade { get; set; } /// /// Gets or sets a value indicating whether the Shotgun weapon shoots twice for each of the two shots. /// public bool UpgradedShotgun { get; set; } /// /// Gets or sets a value indicating whether cluster weapon explode into more clusters. /// public bool UpgradedCluster { get; set; } /// /// Gets or sets a value indicating whether the Longbow weapon is more powerful. /// public bool UpgradedLongbow { get; set; } /// /// Gets or sets a value indicating whether team weapons will be given to the teams, overriding the default /// weapon settings for them. /// public bool EnableTeamWeapons { get; set; } /// /// Gets or sets a value indicating whether super weapons can be collected from crates. /// public bool EnableSuperWeapons { get; set; } /// /// Gets the array of instances, each mapping to one weapon at the index of /// the enumeration. Depending on the scheme , super weapons /// might not be stored in this array. /// public SchemeWeaponSetting[] Weapons { get; set; } // ---- RubberWorm Settings ---- /// /// Gets or sets a value whether power is unlocked like in TestStuff. Configurable with the /alp command. /// public bool RwAntiLockPower { get; set; } /// /// Gets or sets a value indicating whether worms falling into water will be reset to the last solid location /// they stood on. Configurable with the /antisink command. /// public bool RwAntiSink { get; set; } /// /// Gets or sets a value indicating whether the aim direction of a weapon will be reset at turn start. /// Configurable with the /reaim command. /// public bool RwAutoReaim { get; set; } /// /// Gets or sets a value whether the aim of a weapon loops in full rather than half circles. Configurable with /// the /cira command. /// public bool RwCircularAim { get; set; } /// /// Gets or sets the maximum number of crates existing on the map at the same time. 0 disables this feature, /// default limit is 5. Configurable with the /cratelimit command. /// public byte RwCrateLimit { get; set; } /// /// Gets or sets the maximum number of crates spawning per turn and enables the crate counter. Configurable with /// the /craterate command. /// public byte RwCrateRate { get; set; } /// /// Gets or sets a value indicating whether crate shower is enabled throughout a turn. Configurable with the /// /crateshower command. /// public bool RwCrateShower { get; set; } /// /// Gets or sets a value indicating whether weapon fuses can be selected between 0-9 seconds and herd counts /// between 1-10 animals. Configurable with the /fuseex command. /// public bool RwExtendedFuse { get; set; } /// /// Gets or sets a value indicating whether the turn timer is paused while launching a weapon. Configurable with /// the /fdpt or /nopause commands. /// public bool RwFireDoesntPauseTimer { get; set; } /// /// Gets or sets the maximum number of flame particles active at the same time in the scale of 100x. /// Configurable with the /flames command. /// public byte RwFlameLimit { get; set; } /// /// Gets or sets the friction deaccelerating objects touching solid ground. 96 is default, 100 is no friction, /// higher values accelerate objects. Configurable with the /friction command. /// public byte RwFriction { get; set; } /// /// Gets or sets the amount of gravity. Ranges from -64-63 where 0 and 12 are default gravity and negative /// values pull objects upwards. Configurable with the /gravity command. When set, /// and are reset. /// public sbyte RwGravity { get { return _rwGravity; } set { if (value != 0) { RwGravityConstBlackHole = 0; RwGravityPropBlackHole = 0; } _rwGravity = value; } } /// /// gets or sets the amount of gravity acting as a constant black hole. Ranges from -32 to 31. Configurable with /// the /cbh command. When set, and are reset. /// public sbyte RwGravityConstBlackHole { get { return _rwGravityConstBlackHole; } set { if (value != 0) { RwGravity = 0; RwGravityPropBlackHole = 0; } _rwGravityConstBlackHole = value; } } /// /// Gets or sets the amount of gravity acting as a proportional black hole. Ranges from -32 to 31. Configurable /// with the /pbh command. When set, and are /// reset. /// public sbyte RwGravityPropBlackHole { get { return _rwGravityPropBlackHole; } set { if (value != 0) { RwGravity = 0; RwGravityConstBlackHole = 0; } _rwGravityPropBlackHole = value; } } /// /// Gets or sets the Kaos game scheme mod. 0 for none, 1-5 for the corresponding mod. Configurable with the /// /kaosmod command. /// public byte RwKaosMod { get { return _rwKaosMod; } set { if (value > 0xF) { throw new ArgumentException("Kaos mod must not be greater than 15."); } _rwKaosMod = value; } } /// /// Gets or sets the rope knocking power in percent, where 100 is the default power. Configurable with the /// /knock command. /// public byte RwKnockForce { get; set; } /// /// Gets or sets a value indicating whether the loss of control while moving a worm ends does no longer end the /// turn and allows the player to continue moving. Configurable with the /ldet or /stoicworm commands. /// public bool RwLoseControlDoesntEndTurn { get; set; } /// /// Gets or sets the maximum speed a player can reach while roping. 16 is default, 32 is like in TestStuff, 255 /// is unlimited. Configurable with the /speed command. /// public byte RwMaxRopeSpeed { get; set; } /// /// Gets or sets a value indicating whether using a weapon does no longer end the turn and allows to shoot /// multiple weapons in one turn. Configurable with the /sdet or /multishot commands. /// public bool RwShotDoesntEndTurn { get; set; } /// /// Gets or sets a value indicating whether the weapons Armageddon, Earthquake and Indian Nuke Test are affected /// by aswell. Configurable with the /usw command. /// public bool RwShotDoesntEndTurnAll { get; set; } /// /// Gets or sets a value indicating whether all objects are pushed by explosions near them. Configurable with /// the /ope command. /// public bool RwObjectPushByExplosion { get; set; } /// /// Gets or sets a value indicating whether worms can be selected at any time during the turn, as long as worm /// selection is activated. Configurable with the /swat command. /// public bool RwSelectWormAnytime { get; set; } /// /// Gets or sets a value indicating whether the Ninja Rope is more powerful. Configurable with the /ir or /rope+ /// commands. /// public bool RwUpgradedRope { get; set; } /// /// Gets or sets the (special) game version to be forced. Configurable with the /version command. /// S. http://worms2d.info/List_of_Worms_Armageddon_logic_versions. /// public ushort RwVersionOverride { get; set; } /// /// Gets or sets the amount of air viscosity affecting objects. Odd numbers affect worms too. Configurable with /// the /visc command. /// public byte RwViscosity { get; set; } /// /// Gets or sets a value indicating whether the selected weapon no longer resets to a remembered one upon /// shooting it. Configurable with the /wdca command. /// public bool RwWeaponsDontChange { get; set; } /// /// Gets or sets the influence power of the wind affecting all weapons. 255 is the same influence as the /// Bazooka. Odd numbers affect worms too. Configurable with the /wind command. /// public byte RwWindPower { get; set; } /// /// Gets or sets the power with which worms bounce off terrain, where 0 disables this feature and 255 fully /// bounces worms back without speed loss. Configurable with the /rubber command. /// public byte RwWormBouncyness { get; set; } // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- /// /// Loads the data from the given . /// /// The to load the data from. public void Load(Stream stream) { using (BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true)) { // Read the header. if (reader.ReadString(_signature.Length) != _signature) { throw new InvalidDataException("Invalid WSC file signature."); } Version = reader.ReadEnum(true); // Read the options. HotSeatDelay = reader.Read1Byte(); RetreatTime = reader.Read1Byte(); RetreatTimeRope = reader.Read1Byte(); ShowRoundTime = reader.ReadBoolean(); AutomaticReplays = reader.ReadBoolean(); FallDamage = (SchemeFallDamage)(reader.ReadByte() * 50 % 0x100 * 2); ArtilleryMode = reader.ReadBoolean(); SchemeEditor = reader.ReadEnum(false); StockpilingMode = reader.ReadEnum(true); WormSelectMode = reader.ReadEnum(true); SuddenDeathEvent = reader.ReadEnum(true); WaterRiseRate = (SchemeWaterRise)(Math.Pow(reader.ReadByte(), 2) * 5 % 0x100); WeaponCrateProbability = reader.ReadSByte(); DonorCards = reader.ReadBoolean(); HealthCrateProbability = reader.ReadSByte(); HealthCrateEnergy = reader.Read1Byte(); UtilityCrateProbability = reader.ReadSByte(); LoadObjectTypesAndCount(reader); LoadMineDelayConfig(reader); DudMines = reader.ReadBoolean(); ManualWormPlacement = reader.ReadBoolean(); WormEnergy = reader.Read1Byte(); LoadTurnTimeConfig(reader); LoadRoundTimeConfig(reader); NumberOfWins = (byte)Math.Max(1, reader.ReadByte()); Blood = reader.ReadBoolean(); 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(); // Read the weapon settings. Old versions do not store super weapon settings. Weapons = new SchemeWeaponSetting[64]; int weaponCount = GetWeaponCount(); for (int i = 0; i < weaponCount; i++) { Weapons[i] = reader.ReadStruct(); } // Ignore possible unknown WWP trash at the end of the file. // Parse the RubberWorm settings. LoadRubberWormSettings(); } } /// /// Loads the data from the given file. /// /// The name of the file to load the data from. public void Load(string fileName) { using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { Load(stream); } } /// /// Saves the data into the given . /// /// The to save the data to. public void Save(Stream stream) { Save(stream, SchemeSaveFormat.ExtendedWithObjectCount); } /// /// Saves the data into the given with the specified . /// /// The to save the data to. /// The to respect when storing the settings. public void Save(Stream stream, SchemeSaveFormat format) { using (BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII)) { // Write the header. writer.Write(_signature, StringCoding.Raw); writer.Write((byte)Version); // Write the options. writer.Write(HotSeatDelay); writer.Write(RetreatTime); writer.Write(RetreatTimeRope); writer.Write(ShowRoundTime); writer.Write(AutomaticReplays); writer.Write((byte)((int)FallDamage / 4 * 41 % 0x80)); writer.Write(ArtilleryMode); writer.WriteEnum(SchemeEditor, false); writer.WriteEnum(StockpilingMode, true); writer.WriteEnum(WormSelectMode, true); writer.WriteEnum(SuddenDeathEvent, true); writer.Write(_mapWaterRiseToRaw[(byte)WaterRiseRate]); writer.Write(WeaponCrateProbability); writer.Write(DonorCards); writer.Write(HealthCrateProbability); writer.Write(HealthCrateEnergy); writer.Write(UtilityCrateProbability); SaveObjectTypesAndCount(writer, format); SaveMineDelayConfig(writer); writer.Write(DudMines); writer.Write(ManualWormPlacement); writer.Write(WormEnergy); SaveTurnTimeConfig(writer); SaveRoundTimeConfig(writer); writer.Write(NumberOfWins); writer.Write(Blood); 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); // Transfer the RubberWorm settings to unused weapon configuration. SaveRubberWormSettings(); // Write the weapon settings. Old versions do not store super weapon settings. int weaponCount = GetWeaponCount(); foreach (SchemeWeaponSetting weapon in Weapons) { writer.WriteStruct(weapon); } // Ignore possible unknown WWP trash at the end of the file. } } /// /// Saves the data in the given file. /// /// The name of the file to save the data in. public void Save(string fileName) { Save(fileName, SchemeSaveFormat.ExtendedWithObjectCount); } /// /// Saves the data in the given file with the specified . /// /// The name of the file to save the data in. /// The to respect when storing the settings. public void Save(string fileName, SchemeSaveFormat format) { using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) { Save(stream, format); } } // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private void LoadObjectTypesAndCount(BinaryStream reader) { // Invalid values default to 8 mines. ObjectTypes = SchemeObjectType.Mines; ObjectCount = SchemeObjectCount.Count8; byte raw = reader.Read1Byte(); if (raw < 12) { // WA before 3.6.28.0 and WWP only store object type. switch (raw) { case 0x00: ObjectTypes = SchemeObjectType.None; break; case 0x02: ObjectTypes = SchemeObjectType.OilDrums; break; case 0x05: ObjectTypes = SchemeObjectType.Mines | SchemeObjectType.OilDrums; break; } } else { // WA since 3.6.28.0 encodes object type and count in one byte. int modulo = raw % 4; switch (modulo) { case 0x00: ObjectTypes = SchemeObjectType.None; break; case 0x02: ObjectTypes = SchemeObjectType.OilDrums; break; case 0x03: ObjectTypes = SchemeObjectType.Mines | SchemeObjectType.OilDrums; break; } ObjectCount = (SchemeObjectCount)_objectCounts[(raw - (8 + modulo)) / 4]; } } private void LoadMineDelayConfig(BinaryStream reader) { byte raw = reader.Read1Byte(); if (raw == 4 || raw > 0x7F) { MineDelay = 0; MineDelayRandom = true; } else { MineDelay = raw; MineDelayRandom = false; } } private void LoadTurnTimeConfig(BinaryStream reader) { byte raw = reader.Read1Byte(); if (raw > 0x7F) { TurnTime = 0; TurnTimeInfinite = true; } else { TurnTime = raw; TurnTimeInfinite = false; } } private void LoadRoundTimeConfig(BinaryStream reader) { byte raw = reader.Read1Byte(); if (raw > 0x7F) { RoundTimeMinutes = 0; RoundTimeSeconds = (byte)(raw - 0x7F); } else { RoundTimeMinutes = raw; RoundTimeSeconds = 0; } } private void LoadRubberWormSettings() { RwEarthquakeProb earthquakeProb = (RwEarthquakeProb)Weapons[(int)SchemeWeapon.Earthquake].Probability; RwAntiLockPower = earthquakeProb.HasFlag(RwEarthquakeProb.AntiLockPower); RwAutoReaim = earthquakeProb.HasFlag(RwEarthquakeProb.AutoReaim); RwCircularAim = earthquakeProb.HasFlag(RwEarthquakeProb.CircularAim); RwShotDoesntEndTurnAll = earthquakeProb.HasFlag(RwEarthquakeProb.ShotDoesntEndTurnAll); RwKaosMod = ((byte)earthquakeProb).DecodeByte(4, 4); RwAntiSink = Weapons[(int)SchemeWeapon.SheepStrike].Probability != 0; RwCrateLimit = (byte)Weapons[(int)SchemeWeapon.MagicBullet].Probability; RwCrateRate = (byte)Weapons[(int)SchemeWeapon.NuclearTest].Probability; RwMoleSquadronProb moleSquadronProb = (RwMoleSquadronProb)Weapons[(int)SchemeWeapon.MoleSquadron] .Probability; RwCrateShower = moleSquadronProb.HasFlag(RwMoleSquadronProb.CrateShower); RwExtendedFuse = moleSquadronProb.HasFlag(RwMoleSquadronProb.ExtendedFuse); RwFireDoesntPauseTimer = moleSquadronProb.HasFlag(RwMoleSquadronProb.FireDoesntPauseTimer); RwLoseControlDoesntEndTurn = moleSquadronProb.HasFlag(RwMoleSquadronProb.LoseControlDoesntEndTurn); RwObjectPushByExplosion = moleSquadronProb.HasFlag(RwMoleSquadronProb.ObjectPushByExplosion); RwShotDoesntEndTurn = moleSquadronProb.HasFlag(RwMoleSquadronProb.ShotDoesntEndTurn); RwUpgradedRope = moleSquadronProb.HasFlag(RwMoleSquadronProb.UpgradedRope); RwWeaponsDontChange = moleSquadronProb.HasFlag(RwMoleSquadronProb.WeaponsDontChange); RwFlameLimit = (byte)Weapons[(int)SchemeWeapon.ScalesOfJustice].Probability; RwFriction = (byte)Weapons[(int)SchemeWeapon.SalvationArmy].Probability; // 8th and 7th bit control constant / proportional black hole gravity, otherwise normal gravity. byte mailStrikeProb = (byte)Weapons[(int)SchemeWeapon.MailStrike].Probability; if (mailStrikeProb.GetBit(7)) { if (mailStrikeProb.GetBit(6)) { RwGravityPropBlackHole = mailStrikeProb.DecodeSByte(6); } else { RwGravityConstBlackHole = mailStrikeProb.DecodeSByte(6); } } else { RwGravity = mailStrikeProb.DecodeSByte(7); } RwKnockForce = (byte)Weapons[(int)SchemeWeapon.SuperBananaBomb].Probability; RwMaxRopeSpeed = (byte)Weapons[(int)SchemeWeapon.MineStrike].Probability; RwSelectWormAnytime = ((byte)Weapons[(int)SchemeWeapon.MBBomb].Probability).GetBit(1); byte[] versionBytes = new byte[2]; versionBytes[0] = (byte)Weapons[(int)SchemeWeapon.SelectWorm].Probability; versionBytes[1] = (byte)Weapons[(int)SchemeWeapon.Freeze].Probability; RwVersionOverride = BitConverter.ToUInt16(versionBytes, 0); RwViscosity = (byte)Weapons[(int)SchemeWeapon.ConcreteDonkey].Probability; RwWindPower = (byte)Weapons[(int)SchemeWeapon.SuicideBomber].Probability; RwWormBouncyness = (byte)Weapons[(int)SchemeWeapon.Armageddon].Probability; } private int GetWeaponCount() { // Old versions do not store super weapon settings. return Version == SchemeVersion.Extended ? 64 : 45; } private void SaveObjectTypesAndCount(BinaryStream writer, SchemeSaveFormat format) { byte raw = 0; if (format == SchemeSaveFormat.ExtendedWithObjectCount) { // WA since 3.6.28.0 encodes object type and count in one byte. switch (ObjectTypes) { case SchemeObjectType.Mines: raw = 0x01; break; case SchemeObjectType.OilDrums: raw = 0x02; break; case SchemeObjectType.Mines | SchemeObjectType.OilDrums: raw = 0x03; break; } // Get the index of the object count and compute the raw value from that. int index = Array.IndexOf(_objectCounts, (byte)ObjectCount); raw = (byte)(index * 4 + 8 + raw); } else { // WA before 3.6.28.0 and WWP only store object type. switch (ObjectTypes) { case SchemeObjectType.Mines: raw = 0x01; break; case SchemeObjectType.OilDrums: raw = 0x02; break; case SchemeObjectType.Mines | SchemeObjectType.OilDrums: raw = 0x05; break; } } writer.Write(raw); } private void SaveMineDelayConfig(BinaryStream writer) { if (MineDelayRandom) { writer.Write((byte)4); } else { writer.Write(MineDelay); } } private void SaveTurnTimeConfig(BinaryStream writer) { if (TurnTimeInfinite) { writer.Write((byte)0xFF); } else { writer.Write(TurnTime); } } private void SaveRoundTimeConfig(BinaryStream writer) { if (RoundTimeSeconds > 0) { writer.Write((byte)(0xFF - (RoundTimeSeconds - 1))); } else { writer.Write(RoundTimeMinutes); } } private void SaveRubberWormSettings() { byte earthquakeProb = 0; if (RwAntiLockPower) earthquakeProb |= (byte)RwEarthquakeProb.AntiLockPower; if (RwAutoReaim) earthquakeProb |= (byte)RwEarthquakeProb.AutoReaim; if (RwCircularAim) earthquakeProb |= (byte)RwEarthquakeProb.CircularAim; if (RwShotDoesntEndTurnAll) earthquakeProb |= (byte)RwEarthquakeProb.ShotDoesntEndTurnAll; earthquakeProb = earthquakeProb.Encode(RwKaosMod, 4); Weapons[(int)SchemeWeapon.Earthquake].Probability = (sbyte)earthquakeProb; Weapons[(int)SchemeWeapon.SheepStrike].Probability = (sbyte)(RwAntiSink ? 1 : 0); Weapons[(int)SchemeWeapon.MagicBullet].Probability = (sbyte)RwCrateLimit; Weapons[(int)SchemeWeapon.NuclearTest].Probability = (sbyte)RwCrateRate; RwMoleSquadronProb moleSquadronProb = RwMoleSquadronProb.None; if (RwCrateShower) moleSquadronProb |= RwMoleSquadronProb.CrateShower; if (RwExtendedFuse) moleSquadronProb |= RwMoleSquadronProb.ExtendedFuse; if (RwFireDoesntPauseTimer) moleSquadronProb |= RwMoleSquadronProb.FireDoesntPauseTimer; if (RwLoseControlDoesntEndTurn) moleSquadronProb |= RwMoleSquadronProb.LoseControlDoesntEndTurn; if (RwObjectPushByExplosion) moleSquadronProb |= RwMoleSquadronProb.ObjectPushByExplosion; if (RwShotDoesntEndTurn) moleSquadronProb |= RwMoleSquadronProb.ShotDoesntEndTurn; if (RwUpgradedRope) moleSquadronProb |= RwMoleSquadronProb.UpgradedRope; if (RwWeaponsDontChange) moleSquadronProb |= RwMoleSquadronProb.WeaponsDontChange; Weapons[(int)SchemeWeapon.MoleSquadron].Probability = (sbyte)moleSquadronProb; Weapons[(int)SchemeWeapon.ScalesOfJustice].Probability = (sbyte)RwFlameLimit; Weapons[(int)SchemeWeapon.SalvationArmy].Probability = (sbyte)RwFriction; // 8th and 7th bit control constant / proportional black hole gravity, otherwise normal gravity. byte mailStrikeProb = 0; if (RwGravity != 0) { mailStrikeProb = mailStrikeProb.Encode(RwGravity, 7); } else if (RwGravityConstBlackHole != 0) { mailStrikeProb = mailStrikeProb.EnableBit(7); mailStrikeProb = mailStrikeProb.Encode(RwGravityConstBlackHole, 6); } else if (RwGravityPropBlackHole != 0) { mailStrikeProb = mailStrikeProb.EnableBit(7); mailStrikeProb = mailStrikeProb.EnableBit(6); mailStrikeProb = mailStrikeProb.Encode(RwGravityPropBlackHole, 6); } Weapons[(int)SchemeWeapon.MailStrike].Probability = (sbyte)mailStrikeProb; Weapons[(int)SchemeWeapon.SuperBananaBomb].Probability = (sbyte)RwKnockForce; Weapons[(int)SchemeWeapon.MineStrike].Probability = (sbyte)RwMaxRopeSpeed; byte mbBombProb = ((byte)Weapons[(int)SchemeWeapon.MBBomb].Probability).SetBit(0, RwSelectWormAnytime); Weapons[(int)SchemeWeapon.MBBomb].Probability = (sbyte)mbBombProb; byte[] versionBytes = BitConverter.GetBytes(RwVersionOverride); Weapons[(int)SchemeWeapon.SelectWorm].Probability = (sbyte)versionBytes[0]; Weapons[(int)SchemeWeapon.Freeze].Probability = (sbyte)versionBytes[1]; Weapons[(int)SchemeWeapon.ConcreteDonkey].Probability = (sbyte)RwViscosity; Weapons[(int)SchemeWeapon.SuicideBomber].Probability = (sbyte)RwWindPower; Weapons[(int)SchemeWeapon.Armageddon].Probability = (sbyte)RwWormBouncyness; } // ---- ENUMERATIONS ------------------------------------------------------------------------------------------- [Flags] private enum RwEarthquakeProb : byte { None = 0, AutoReaim = 1 << 0, CircularAim = 1 << 1, AntiLockPower = 1 << 2, ShotDoesntEndTurnAll = 1 << 3 // Remaining bits represent kaosmod version. } [Flags] private enum RwMoleSquadronProb : byte { None = 0, ShotDoesntEndTurn = 1 << 0, LoseControlDoesntEndTurn = 1 << 1, FireDoesntPauseTimer = 1 << 2, UpgradedRope = 1 << 3, CrateShower = 1 << 4, ObjectPushByExplosion = 1 << 5, WeaponsDontChange = 1 << 6, ExtendedFuse = 1 << 7 } } }