diff --git a/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs b/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs index 6cc4b75e34..a4d77a0209 100644 --- a/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs +++ b/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs @@ -49,7 +49,7 @@ namespace OpenRA.Mods.Cnc.FileFormats Transforms[c + ids[k]] = s.ReadFloat(); Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16); - if (Util.MatrixInverse(testMatrix) == null) + if (OpenRA.Graphics.Util.MatrixInverse(testMatrix) == null) throw new InvalidDataException( "The transformation matrix for HVA file `{0}` section {1} frame {2} is invalid because it is not invertible!" .F(fileName, i, j)); diff --git a/OpenRA.Mods.Cnc/Graphics/ClassicSpriteSequence.cs b/OpenRA.Mods.Cnc/Graphics/ClassicSpriteSequence.cs new file mode 100644 index 0000000000..fcaefee11c --- /dev/null +++ b/OpenRA.Mods.Cnc/Graphics/ClassicSpriteSequence.cs @@ -0,0 +1,50 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; + +namespace OpenRA.Mods.Cnc.Graphics +{ + public class ClassicSpriteSequenceLoader : DefaultSpriteSequenceLoader + { + public ClassicSpriteSequenceLoader(ModData modData) + : base(modData) { } + + public override ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info) + { + return new ClassicSpriteSequence(modData, tileSet, cache, this, sequence, animation, info); + } + } + + public class ClassicSpriteSequence : DefaultSpriteSequence + { + readonly bool useClassicFacings; + + public ClassicSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info) + : base(modData, tileSet, cache, loader, sequence, animation, info) + { + var d = info.ToDictionary(); + useClassicFacings = LoadField(d, "UseClassicFacings", false); + + if (useClassicFacings && Facings != 32) + throw new InvalidOperationException( + "{0}: Sequence {1}.{2}: UseClassicFacings is only valid for 32 facings" + .F(info.Nodes[0].Location, sequence, animation)); + } + + protected override int QuantizeFacing(int facing) + { + return OpenRA.Mods.Cnc.Util.ClassicQuantizeFacing(facing, Facings, useClassicFacings); + } + } +} diff --git a/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs b/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs new file mode 100644 index 0000000000..ae547975ab --- /dev/null +++ b/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs @@ -0,0 +1,92 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; + +namespace OpenRA.Mods.Cnc.Graphics +{ + public class ClassicTilesetSpecificSpriteSequenceLoader : ClassicSpriteSequenceLoader + { + public readonly string DefaultSpriteExtension = ".shp"; + public readonly Dictionary TilesetExtensions = new Dictionary(); + public readonly Dictionary TilesetCodes = new Dictionary(); + + public ClassicTilesetSpecificSpriteSequenceLoader(ModData modData) + : base(modData) + { + var metadata = modData.Manifest.Get().Metadata; + MiniYaml yaml; + if (metadata.TryGetValue("DefaultSpriteExtension", out yaml)) + DefaultSpriteExtension = yaml.Value; + + if (metadata.TryGetValue("TilesetExtensions", out yaml)) + TilesetExtensions = yaml.ToDictionary(kv => kv.Value); + + if (metadata.TryGetValue("TilesetCodes", out yaml)) + TilesetCodes = yaml.ToDictionary(kv => kv.Value); + } + + public override ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info) + { + return new ClassicTilesetSpecificSpriteSequence(modData, tileSet, cache, this, sequence, animation, info); + } + } + + public class ClassicTilesetSpecificSpriteSequence : ClassicSpriteSequence + { + public ClassicTilesetSpecificSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info) + : base(modData, tileSet, cache, loader, sequence, animation, info) { } + + string ResolveTilesetId(TileSet tileSet, Dictionary d) + { + var tsId = tileSet.Id; + + MiniYaml yaml; + if (d.TryGetValue("TilesetOverrides", out yaml)) + { + var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tsId); + if (tsNode != null) + tsId = tsNode.Value.Value; + } + + return tsId; + } + + protected override string GetSpriteSrc(ModData modData, TileSet tileSet, string sequence, string animation, string sprite, Dictionary d) + { + var loader = (ClassicTilesetSpecificSpriteSequenceLoader)Loader; + + var spriteName = sprite ?? sequence; + + if (LoadField(d, "UseTilesetCode", false)) + { + string code; + if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out code)) + spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2); + } + + if (LoadField(d, "AddExtension", true)) + { + var useTilesetExtension = LoadField(d, "UseTilesetExtension", false); + + string tilesetExtension; + if (useTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out tilesetExtension)) + return spriteName + tilesetExtension; + + return spriteName + loader.DefaultSpriteExtension; + } + + return spriteName; + } + } +} diff --git a/OpenRA.Mods.Cnc/Graphics/Voxel.cs b/OpenRA.Mods.Cnc/Graphics/Voxel.cs index 1d6ff2e0dc..e1c29ff4ca 100644 --- a/OpenRA.Mods.Cnc/Graphics/Voxel.cs +++ b/OpenRA.Mods.Cnc/Graphics/Voxel.cs @@ -74,8 +74,8 @@ namespace OpenRA.Mods.Cnc.Graphics t[14] *= l.Scale * (l.Bounds[5] - l.Bounds[2]) / l.Size[2]; // Center, flip and scale - t = Util.MatrixMultiply(t, Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2])); - t = Util.MatrixMultiply(Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t); + t = OpenRA.Graphics.Util.MatrixMultiply(t, OpenRA.Graphics.Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2])); + t = OpenRA.Graphics.Util.MatrixMultiply(OpenRA.Graphics.Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t); return t; } @@ -119,7 +119,7 @@ namespace OpenRA.Mods.Cnc.Graphics }; // Calculate limb bounding box - var bb = Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b); + var bb = OpenRA.Graphics.Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b); for (var i = 0; i < 3; i++) { ret[i] = Math.Min(ret[i], bb[i]); diff --git a/OpenRA.Mods.Cnc/Graphics/VoxelLoader.cs b/OpenRA.Mods.Cnc/Graphics/VoxelLoader.cs index 56fa9916d5..f6c3b473e3 100644 --- a/OpenRA.Mods.Cnc/Graphics/VoxelLoader.cs +++ b/OpenRA.Mods.Cnc/Graphics/VoxelLoader.cs @@ -77,8 +77,8 @@ namespace OpenRA.Mods.Cnc.Graphics var size = new Size(su, sv); var s = sheetBuilder.Allocate(size); var t = sheetBuilder.Allocate(size); - Util.FastCopyIntoChannel(s, colors); - Util.FastCopyIntoChannel(t, normals); + OpenRA.Graphics.Util.FastCopyIntoChannel(s, colors); + OpenRA.Graphics.Util.FastCopyIntoChannel(t, normals); // s and t are guaranteed to use the same sheet because // of the custom voxel sheet allocation implementation diff --git a/OpenRA.Mods.Cnc/Traits/ClassicFacingBodyOrientation.cs b/OpenRA.Mods.Cnc/Traits/ClassicFacingBodyOrientation.cs new file mode 100644 index 0000000000..feb2045678 --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/ClassicFacingBodyOrientation.cs @@ -0,0 +1,32 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using OpenRA.Mods.Common.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [Desc("Fudge the coordinate system angles like the early games (for sprite sequences that use classic facing fudge).")] + public class ClassicFacingBodyOrientationInfo : BodyOrientationInfo + { + public override int QuantizeFacing(int facing, int facings) + { + return OpenRA.Mods.Cnc.Util.ClassicQuantizeFacing(facing, facings, true) * (256 / facings); + } + + public override object Create(ActorInitializer init) { return new ClassicFacingBodyOrientation(init, this); } + } + + public class ClassicFacingBodyOrientation : BodyOrientation + { + public ClassicFacingBodyOrientation(ActorInitializer init, ClassicFacingBodyOrientationInfo info) + : base(init, info) { } + } +} diff --git a/OpenRA.Mods.Cnc/Traits/ResourcePurifier.cs b/OpenRA.Mods.Cnc/Traits/ResourcePurifier.cs index 775e268a35..e4d84c6d61 100644 --- a/OpenRA.Mods.Cnc/Traits/ResourcePurifier.cs +++ b/OpenRA.Mods.Cnc/Traits/ResourcePurifier.cs @@ -67,7 +67,7 @@ namespace OpenRA.Mods.Cnc.Traits if (IsTraitDisabled) return; - var cash = Util.ApplyPercentageModifiers(amount, modifier); + var cash = OpenRA.Mods.Common.Util.ApplyPercentageModifiers(amount, modifier); playerResources.GiveCash(cash); if (Info.ShowTicks && self.Info.HasTraitInfo()) diff --git a/OpenRA.Mods.Cnc/Traits/TDGunboat.cs b/OpenRA.Mods.Cnc/Traits/TDGunboat.cs index a332b1c305..e4d79e52cb 100644 --- a/OpenRA.Mods.Cnc/Traits/TDGunboat.cs +++ b/OpenRA.Mods.Cnc/Traits/TDGunboat.cs @@ -134,7 +134,7 @@ namespace OpenRA.Mods.Cnc.Traits int MovementSpeed { - get { return Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); } + get { return OpenRA.Mods.Common.Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); } } public Pair[] OccupiedCells() { return new[] { Pair.New(TopLeft, SubCell.FullCell) }; } diff --git a/OpenRA.Mods.Cnc/Util.cs b/OpenRA.Mods.Cnc/Util.cs new file mode 100644 index 0000000000..d517370b61 --- /dev/null +++ b/OpenRA.Mods.Cnc/Util.cs @@ -0,0 +1,40 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +namespace OpenRA.Mods.Cnc +{ + public static class Util + { + public static int ClassicQuantizeFacing(int facing, int numFrames, bool useClassicFacingFudge) + { + if (!useClassicFacingFudge || numFrames != 32) + return OpenRA.Mods.Common.Util.QuantizeFacing(facing, numFrames); + + // TD and RA divided the facing artwork into 3 frames from (north|south) to (north|south)-(east|west) + // and then 5 frames from (north|south)-(east|west) to (east|west) + var quadrant = ((facing + 31) & 0xFF) / 64; + if (quadrant == 0 || quadrant == 2) + { + var frame = OpenRA.Mods.Common.Util.QuantizeFacing(facing, 24); + if (frame > 18) + return frame + 6; + if (frame > 4) + return frame + 3; + return frame; + } + else + { + var frame = OpenRA.Mods.Common.Util.QuantizeFacing(facing, 40); + return frame < 20 ? frame - 3 : frame - 8; + } + } + } +} diff --git a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs index a4c0ec64d2..d3687664ea 100644 --- a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs +++ b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs @@ -95,11 +95,11 @@ namespace OpenRA.Mods.Common.Graphics { static readonly WDist DefaultShadowSpriteZOffset = new WDist(-5); protected Sprite[] sprites; - readonly bool reverseFacings, transpose, useClassicFacingFudge; + readonly bool reverseFacings, transpose; + readonly string sequence; protected readonly ISpriteSequenceLoader Loader; - readonly string sequence; public string Name { get; private set; } public int Start { get; private set; } public int Length { get; private set; } @@ -156,7 +156,6 @@ namespace OpenRA.Mods.Common.Graphics Tick = LoadField(d, "Tick", 40); transpose = LoadField(d, "Transpose", false); Frames = LoadField(d, "Frames", null); - useClassicFacingFudge = LoadField(d, "UseClassicFacingFudge", false); var flipX = LoadField(d, "FlipX", false); var flipY = LoadField(d, "FlipY", false); @@ -168,11 +167,6 @@ namespace OpenRA.Mods.Common.Graphics Facings = -Facings; } - if (useClassicFacingFudge && Facings != 32) - throw new InvalidOperationException( - "{0}: Sequence {1}.{2}: UseClassicFacingFudge is only valid for 32 facings" - .F(info.Nodes[0].Location, sequence, animation)); - var offset = LoadField(d, "Offset", float3.Zero); var blendMode = LoadField(d, "BlendMode", BlendMode.Alpha); @@ -384,7 +378,7 @@ namespace OpenRA.Mods.Common.Graphics protected virtual Sprite GetSprite(int start, int frame, int facing) { - var f = Util.QuantizeFacing(facing, Facings, useClassicFacingFudge); + var f = QuantizeFacing(facing); if (reverseFacings) f = (Facings - f) % Facings; @@ -398,5 +392,10 @@ namespace OpenRA.Mods.Common.Graphics return sprites[j]; } + + protected virtual int QuantizeFacing(int facing) + { + return Util.QuantizeFacing(facing, Facings); + } } } diff --git a/OpenRA.Mods.Common/Traits/BodyOrientation.cs b/OpenRA.Mods.Common/Traits/BodyOrientation.cs index 1173e2eebc..a858508297 100644 --- a/OpenRA.Mods.Common/Traits/BodyOrientation.cs +++ b/OpenRA.Mods.Common/Traits/BodyOrientation.cs @@ -17,25 +17,22 @@ namespace OpenRA.Mods.Common.Traits { public class BodyOrientationInfo : ITraitInfo { - [Desc("Number of facings for gameplay calculations. -1 indicates auto-detection from another trait")] + [Desc("Number of facings for gameplay calculations. -1 indicates auto-detection from another trait.")] public readonly int QuantizedFacings = -1; - [Desc("Camera pitch for rotation calculations")] + [Desc("Camera pitch for rotation calculations.")] public readonly WAngle CameraPitch = WAngle.FromDegrees(40); - [Desc("Fudge the coordinate system angles like the early games.")] + [Desc("Fudge the coordinate system angles to simulate non-top-down perspective in mods with square cells.")] public readonly bool UseClassicPerspectiveFudge = true; - [Desc("Fudge the coordinate system angles like the early games.")] - public readonly bool UseClassicFacingFudge = false; - public WVec LocalToWorld(WVec vec) { // Rotate by 90 degrees if (!UseClassicPerspectiveFudge) return new WVec(vec.Y, -vec.X, vec.Z); - // RA's 2d perspective doesn't correspond to an orthonormal 3D + // The 2d perspective of older games with square cells doesn't correspond to an orthonormal 3D // coordinate system, so fudge the y axis to make things look good return new WVec(vec.Y, -CameraPitch.Sin() * vec.X / 1024, vec.Z); } @@ -53,12 +50,12 @@ namespace OpenRA.Mods.Common.Traits return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing)); } - public int QuantizeFacing(int facing, int facings) + public virtual int QuantizeFacing(int facing, int facings) { - return Util.QuantizeFacing(facing, facings, UseClassicFacingFudge) * (256 / facings); + return Util.QuantizeFacing(facing, facings) * (256 / facings); } - public object Create(ActorInitializer init) { return new BodyOrientation(init, this); } + public virtual object Create(ActorInitializer init) { return new BodyOrientation(init, this); } } public class BodyOrientation : ISync diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index 5664fab294..bef861098d 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -53,30 +53,6 @@ namespace OpenRA.Mods.Common return a / step; } - public static int QuantizeFacing(int facing, int numFrames, bool useClassicFacingFudge) - { - if (!useClassicFacingFudge || numFrames != 32) - return Util.QuantizeFacing(facing, numFrames); - - // TD and RA divided the facing artwork into 3 frames from (north|south) to (north|south)-(east|west) - // and then 5 frames from (north|south)-(east|west) to (east|west) - var quadrant = ((facing + 31) & 0xFF) / 64; - if (quadrant == 0 || quadrant == 2) - { - var frame = Util.QuantizeFacing(facing, 24); - if (frame > 18) - return frame + 6; - if (frame > 4) - return frame + 3; - return frame; - } - else - { - var frame = Util.QuantizeFacing(facing, 40); - return frame < 20 ? frame - 3 : frame - 8; - } - } - /// Wraps an arbitrary integer facing value into the range 0 - 255 public static int NormalizeFacing(int f) {