diff --git a/CHANGELOG b/CHANGELOG index 80756395ff..6148371f7c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ NEW: Added a new hotkey to select all units on screen (default: CTRL + A). Added a new hotkey to jump to production buildings (default: TAB). Changed default hotkey (PageUp/Down) for build palette cycling and made reverse user configurable. + Improved shroud/fog rendering. Asset Browser: Fixed crashes when trying to load invalid filenames or sprites with just 1 frame. Added support for all sprite types. @@ -73,6 +74,7 @@ NEW: Disabled the main menu target reticle showing when a window is open. Added a display of the faction logos when the shellmap is disabled. Visceriods now heal on Tiberium and move faster there. + Implemented the original shroud artwork. Dune 2000: Added buildable concrete walls. Fixed some cliffs being passable. @@ -80,6 +82,7 @@ NEW: Fixed A* debug overlay. Fixed R8 offsets for sprites with embedded palettes. Implemented proper spice rendering. + Implemented the original shroud artwork. Engine: Replays are now saved in per-mod and per-version folders. Added password protection support for servers. @@ -99,6 +102,7 @@ NEW: Rewritten shp(ts) parser makes more efficient use of texture space. Added support for the dune 2 shp and pak formats. Map format 6 requires the RequiresMod to be defined. + Added a multiplicitive blend mode. Build system and packages: Added GeoIP to Makefile so it is installed properly. Added desktop shortcut creation support to the Makefile and Windows installer. @@ -137,7 +141,8 @@ NEW: Added OpenRA.Utility --map-upgrade for updating maps from format 5 to format 6. The map format has been changed. All user-installed maps will be upgraded on the first mod launch, or using OpenRA.Utility --map-upgrade. Unified sprite loading allows any sprite type to be used anywhere: shp can now be used for terrain, and tmp for units. - Harvestable resource definitions (ResourceTypes) have changed, and now specify their artwork using via sequences. + Harvestable resource definitions (ResourceTypes) have changed, and now specify their artwork using sequences. + Shroud definitions (ShroudRenderer / ShroudPalette) have changed, and now specifies its artwork using sequences. 20130915: All mods: diff --git a/OpenRA.FileFormats/Graphics/IGraphicsDevice.cs b/OpenRA.FileFormats/Graphics/IGraphicsDevice.cs index 16f0b67d39..ae2a280816 100755 --- a/OpenRA.FileFormats/Graphics/IGraphicsDevice.cs +++ b/OpenRA.FileFormats/Graphics/IGraphicsDevice.cs @@ -31,7 +31,7 @@ namespace OpenRA.FileFormats.Graphics IGraphicsDevice Create( Size size, WindowMode windowMode ); } - public enum BlendMode { None, Alpha, Additive, Subtractive } + public enum BlendMode { None, Alpha, Additive, Subtractive, Multiply } public interface IGraphicsDevice { diff --git a/OpenRA.Game/Graphics/ShroudRenderer.cs b/OpenRA.Game/Graphics/ShroudRenderer.cs deleted file mode 100644 index 66a1c0e418..0000000000 --- a/OpenRA.Game/Graphics/ShroudRenderer.cs +++ /dev/null @@ -1,210 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2011 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. For more information, - * see COPYING. - */ -#endregion - -using System.Drawing; -using OpenRA.Traits; - -namespace OpenRA.Graphics -{ - public class ShroudRenderer - { - World world; - Map map; - Sprite[] shadowBits = Game.modData.SpriteLoader.LoadAllSprites("shadow"); - Sprite[,] sprites, fogSprites; - int shroudHash; - - bool initializePalettes = true; - PaletteReference fogPalette, shroudPalette; - - static readonly byte[][] SpecialShroudTiles = - { - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, - new byte[] { 32, 32, 25, 25, 19, 19, 20, 20 }, - new byte[] { 33, 33, 33, 33, 26, 26, 26, 26, 21, 21, 21, 21, 23, 23, 23, 23 }, - new byte[] { 36, 36, 36, 36, 30, 30, 30, 30 }, - new byte[] { 34, 16, 34, 16, 34, 16, 34, 16, 27, 22, 27, 22, 27, 22, 27, 22 }, - new byte[] { 44 }, - new byte[] { 37, 37, 37, 37, 37, 37, 37, 37, 31, 31, 31, 31, 31, 31, 31, 31 }, - new byte[] { 40 }, - new byte[] { 35, 24, 17, 18 }, - new byte[] { 39, 39, 29, 29 }, - new byte[] { 45 }, - new byte[] { 43 }, - new byte[] { 38, 28 }, - new byte[] { 42 }, - new byte[] { 41 }, - new byte[] { 46 }, - }; - - public ShroudRenderer(World world) - { - this.world = world; - this.map = world.Map; - - sprites = new Sprite[map.MapSize.X, map.MapSize.Y]; - fogSprites = new Sprite[map.MapSize.X, map.MapSize.Y]; - - // Force update on first render - shroudHash = -1; - } - - Sprite ChooseShroud(Shroud s, int i, int j) - { - if (!s.IsExplored(i, j)) - return shadowBits[0xf]; - - // bits are for unexploredness: up, right, down, left - var v = 0; - // bits are for unexploredness: TL, TR, BR, BL - var u = 0; - - if (!s.IsExplored(i, j - 1)) { v |= 1; u |= 3; } - if (!s.IsExplored(i + 1, j)) { v |= 2; u |= 6; } - if (!s.IsExplored(i, j + 1)) { v |= 4; u |= 12; } - if (!s.IsExplored(i - 1, j)) { v |= 8; u |= 9; } - - var uSides = u; - if (!s.IsExplored(i - 1, j - 1)) u |= 1; - if (!s.IsExplored(i + 1, j - 1)) u |= 2; - if (!s.IsExplored(i + 1, j + 1)) u |= 4; - if (!s.IsExplored(i - 1, j + 1)) u |= 8; - - return shadowBits[SpecialShroudTiles[u ^ uSides][v]]; - } - - Sprite ChooseFog(Shroud s, int i, int j) - { - if (!s.IsVisible(i, j)) return shadowBits[0xf]; - if (!s.IsExplored(i, j)) return shadowBits[0xf]; - - // bits are for unexploredness: up, right, down, left - var v = 0; - // bits are for unexploredness: TL, TR, BR, BL - var u = 0; - - if (!s.IsVisible(i, j - 1)) { v |= 1; u |= 3; } - if (!s.IsVisible(i + 1, j)) { v |= 2; u |= 6; } - if (!s.IsVisible(i, j + 1)) { v |= 4; u |= 12; } - if (!s.IsVisible(i - 1, j)) { v |= 8; u |= 9; } - - var uSides = u; - - if (!s.IsVisible(i - 1, j - 1)) u |= 1; - if (!s.IsVisible(i + 1, j - 1)) u |= 2; - if (!s.IsVisible(i + 1, j + 1)) u |= 4; - if (!s.IsVisible(i - 1, j + 1)) u |= 8; - - return shadowBits[SpecialShroudTiles[u ^ uSides][v]]; - } - - void GenerateSprites(Shroud shroud) - { - var hash = shroud != null ? shroud.Hash : 0; - if (shroudHash == hash) - return; - - shroudHash = hash; - if (shroud == null) - { - // Players with no shroud see the whole map so we only need to set the edges - var b = map.Bounds; - for (int i = b.Left; i < b.Right; i++) - for (int j = b.Top; j < b.Bottom; j++) - { - var v = 0; - var u = 0; - - if (j == b.Top) { v |= 1; u |= 3; } - if (i == b.Right - 1) { v |= 2; u |= 6; } - if (j == b.Bottom - 1) { v |= 4; u |= 12; } - if (i == b.Left) { v |= 8; u |= 9; } - - var uSides = u; - if (i == b.Left && j == b.Top) u |= 1; - if (i == b.Right - 1 && j == b.Top) u |= 2; - if (i == b.Right - 1 && j == b.Bottom - 1) u |= 4; - if (i == b.Left && j == b.Bottom - 1) u |= 8; - - sprites[i, j] = fogSprites[i, j] = shadowBits[SpecialShroudTiles[u ^ uSides][v]]; - } - } - else - { - for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) - for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) - sprites[i, j] = ChooseShroud(shroud, i, j); - - for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) - for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) - fogSprites[i, j] = ChooseFog(shroud, i, j); - } - } - - internal void Draw(WorldRenderer wr, Shroud shroud) - { - if (initializePalettes) - { - if (world.LobbyInfo.GlobalSettings.Fog) - fogPalette = wr.Palette("fog"); - - shroudPalette = world.LobbyInfo.GlobalSettings.Fog ? wr.Palette("shroud") : wr.Palette("shroudfog"); - initializePalettes = false; - } - - GenerateSprites(shroud); - - // We draw the shroud when disabled to hide the sharp map edges - var clipRect = wr.Viewport.CellBounds; - DrawShroud(wr, clipRect, sprites, shroudPalette); - - if (world.LobbyInfo.GlobalSettings.Fog) - DrawShroud(wr, clipRect, fogSprites, fogPalette); - } - - void DrawShroud(WorldRenderer wr, Rectangle clip, Sprite[,] s, PaletteReference pal) - { - for (var j = clip.Top; j < clip.Bottom; j++) - { - var starti = clip.Left; - var last = shadowBits[0x0f]; - for (var i = clip.Left; i < clip.Right; i++) - { - if ((s[i, j] == shadowBits[0x0f] && last == shadowBits[0x0f]) - || (s[i, j] == shadowBits[0] && last == shadowBits[0])) - continue; - - if (starti != i) - { - // Stretch a solid black sprite over the rows above - // TODO: This doesn't make sense for isometric terrain - Game.Renderer.WorldSpriteRenderer.DrawSprite( - s[starti, j], - Game.CellSize * new float2(starti, j), - pal, - new float2(Game.CellSize * (i - starti), Game.CellSize)); - starti = i + 1; - } - - Game.Renderer.WorldSpriteRenderer.DrawSprite(s[i, j], Game.CellSize * new float2(i, j), pal); - starti = i + 1; - last = s[i, j]; - } - - // Stretch a solid black sprite over the rows to the left - // TODO: This doesn't make sense for isometric terrain - if (starti < clip.Right) - Game.Renderer.WorldSpriteRenderer.DrawSprite(s[starti, j], - Game.CellSize * new float2(starti, j), pal, - new float2(Game.CellSize * (clip.Right - starti), Game.CellSize)); - } - } - } -} diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index 2195bb7b24..c4a6c96679 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -37,7 +37,6 @@ namespace OpenRA.Graphics public Viewport Viewport { get; private set; } internal readonly TerrainRenderer terrainRenderer; - internal readonly ShroudRenderer shroudRenderer; internal readonly HardwarePalette palette; internal Cache palettes; Lazy devTrait; @@ -56,7 +55,6 @@ namespace OpenRA.Graphics Theater = new Theater(world.TileSet); terrainRenderer = new TerrainRenderer(world, this); - shroudRenderer = new ShroudRenderer(world); devTrait = Lazy.New(() => world.LocalPlayer != null ? world.LocalPlayer.PlayerActor.Trait() : null); } @@ -132,7 +130,9 @@ namespace OpenRA.Graphics world.OrderGenerator.RenderAfterWorld(this, world); var renderShroud = world.RenderPlayer != null ? world.RenderPlayer.Shroud : null; - shroudRenderer.Draw(this, renderShroud); + + foreach (var a in world.ActorsWithTrait()) + a.Trait.RenderShroud(this, renderShroud); if (devTrait.Value != null && devTrait.Value.ShowDebugGeometry) for (var i = 0; i < renderables.Count; i++) diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 4830545e52..71489dd06d 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -110,7 +110,6 @@ - diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index d0447ab2f6..c95ef77b3f 100755 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -191,6 +191,7 @@ namespace OpenRA.Traits public interface IBlocksBullets { } public interface IPostRender { void RenderAfterWorld(WorldRenderer wr, Actor self); } + public interface IRenderShroud { void RenderShroud(WorldRenderer wr, Shroud shroud); } public interface IPostRenderSelection { void RenderAfterWorld(WorldRenderer wr); } public interface IBodyOrientation diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index d53f8eeaa7..cf86cfa312 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -263,9 +263,7 @@ namespace OpenRA.Traits public bool IsVisible(CPos xy) { return IsVisible(xy.X, xy.Y); } public bool IsVisible(int x, int y) { - // Visibility is allowed to extend beyond the map cordon so that - // the fog tiles are not visible at the edge of the world - if (x < 0 || x >= map.MapSize.X || y < 0 || y >= map.MapSize.Y) + if (!map.IsInMap(x, y)) return false; if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog) diff --git a/OpenRA.Mods.D2k/FogPaletteFromR8.cs b/OpenRA.Mods.D2k/FogPaletteFromR8.cs new file mode 100644 index 0000000000..ce3b6792c4 --- /dev/null +++ b/OpenRA.Mods.D2k/FogPaletteFromR8.cs @@ -0,0 +1,59 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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. For more information, + * see COPYING. + */ +#endregion + +using System.IO; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + class FogPaletteFromR8Info : ITraitInfo + { + [Desc("Internal palette name")] + public readonly string Name = null; + [Desc("Filename to load")] + public readonly string Filename = null; + [Desc("Palette byte offset")] + public readonly long Offset = 0; + public readonly bool AllowModifiers = true; + public readonly bool InvertColor = false; + + public object Create(ActorInitializer init) { return new FogPaletteFromR8(this); } + } + + class FogPaletteFromR8 : IPalette + { + readonly FogPaletteFromR8Info info; + public FogPaletteFromR8(FogPaletteFromR8Info info) { this.info = info; } + + public void InitPalette(WorldRenderer wr) + { + var colors = new uint[256]; + using (var s = FileSystem.Open(info.Filename)) + { + s.Seek(info.Offset, SeekOrigin.Begin); + + for (var i = 0; i < 256; i++) + { + var packed = s.ReadUInt16(); + + // Fog is rendered with half opacity + colors[i] = (uint)((255 << 24) | ((packed & 0xF800) << 7) | ((packed & 0x7E0) << 4) | ((packed & 0x1f) << 2)); + + if (info.InvertColor) + colors[i] ^= 0x00FFFFFF; + } + } + + wr.AddPalette(info.Name, new Palette(colors), info.AllowModifiers); + } + } +} diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index b6fa211885..3d78f92267 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -80,6 +80,7 @@ + diff --git a/OpenRA.Mods.D2k/PaletteFromR8.cs b/OpenRA.Mods.D2k/PaletteFromR8.cs index 901fef682c..b60a26330b 100644 --- a/OpenRA.Mods.D2k/PaletteFromR8.cs +++ b/OpenRA.Mods.D2k/PaletteFromR8.cs @@ -10,8 +10,8 @@ using System.IO; using OpenRA.FileFormats; -using OpenRA.Traits; using OpenRA.Graphics; +using OpenRA.Traits; namespace OpenRA.Mods.RA { @@ -24,6 +24,7 @@ namespace OpenRA.Mods.RA [Desc("Palette byte offset")] public readonly long Offset = 0; public readonly bool AllowModifiers = true; + public readonly bool InvertColor = false; public object Create(ActorInitializer init) { return new PaletteFromR8(this); } } @@ -42,10 +43,11 @@ namespace OpenRA.Mods.RA for (var i = 0; i < 256; i++) { - // The custom palette is scaled into the range 0-128. - // This makes the move-flash match the original game, but may not be correct in other cases. var packed = s.ReadUInt16(); - colors[i] = (uint)((255 << 24) | ((packed & 0xF800) << 7) | ((packed & 0x7E0) << 4) | ((packed & 0x1f) << 2)); + colors[i] = (uint)((255 << 24) | ((packed & 0xF800) << 8) | ((packed & 0x7E0) << 5) | ((packed & 0x1f) << 3)); + + if (info.InvertColor) + colors[i] ^= 0x00FFFFFF; } } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 224c878321..980c6a412b 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -482,6 +482,7 @@ + diff --git a/OpenRA.Mods.RA/ShroudPalette.cs b/OpenRA.Mods.RA/ShroudPalette.cs index 247a87986e..9f1c8daf6f 100644 --- a/OpenRA.Mods.RA/ShroudPalette.cs +++ b/OpenRA.Mods.RA/ShroudPalette.cs @@ -15,8 +15,6 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { - public enum ShroudPaletteType { Shroud, Fog, Combined }; - [Desc("Adds the hard-coded shroud palette to the game")] class ShroudPaletteInfo : ITraitInfo { @@ -24,7 +22,7 @@ namespace OpenRA.Mods.RA public readonly string Name = "shroud"; [Desc("Palette type")] - public readonly ShroudPaletteType Type = ShroudPaletteType.Combined; + public readonly bool Fog = false; public object Create(ActorInitializer init) { return new ShroudPalette(this); } } @@ -37,35 +35,24 @@ namespace OpenRA.Mods.RA public void InitPalette(WorldRenderer wr) { - var c = info.Type == ShroudPaletteType.Shroud ? Shroud : - info.Type == ShroudPaletteType.Fog ? Fog : Combined; - + var c = info.Fog ? Fog : Shroud; wr.AddPalette(info.Name, new Palette(Exts.MakeArray(256, i => (uint)c[i % 8].ToArgb())), false); } - static Color[] Shroud = new[] { - Color.Transparent, Color.Green, - Color.Blue, Color.Yellow, - Color.Black, - Color.FromArgb(128,0,0,0), - Color.Transparent, - Color.Transparent - }; - static Color[] Fog = new[] { Color.Transparent, Color.Green, Color.Blue, Color.Yellow, Color.FromArgb(128,0,0,0), - Color.FromArgb(128,0,0,0), - Color.FromArgb(128,0,0,0), - Color.FromArgb(64,0,0,0) + Color.FromArgb(96,0,0,0), + Color.FromArgb(64,0,0,0), + Color.FromArgb(32,0,0,0) }; - static Color[] Combined = new[] { + static Color[] Shroud = new[] { Color.Transparent, Color.Green, Color.Blue, Color.Yellow, Color.Black, - Color.FromArgb(192,0,0,0), + Color.FromArgb(160,0,0,0), Color.FromArgb(128,0,0,0), Color.FromArgb(64,0,0,0) }; diff --git a/OpenRA.Mods.RA/ShroudRenderer.cs b/OpenRA.Mods.RA/ShroudRenderer.cs new file mode 100644 index 0000000000..32ec15794c --- /dev/null +++ b/OpenRA.Mods.RA/ShroudRenderer.cs @@ -0,0 +1,267 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.FileFormats.Graphics; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class ShroudRendererInfo : ITraitInfo + { + public string Sequence = "shroud"; + public string[] Variants = new[] { "shroud" }; + + [Desc("Bitfield of shroud directions for each frame. Lower four bits are", + "corners clockwise from TL; upper four are edges clockwise from top")] + public int[] Index = new[] { 12, 9, 8, 3, 1, 6, 4, 2, 13, 11, 7, 14 }; + + [Desc("Use the upper four bits when calculating frame")] + public bool UseExtendedIndex = false; + + [Desc("Palette index for synthesized unexplored tile")] + public int ShroudColor = 12; + public BlendMode ShroudBlend = BlendMode.Alpha; + public object Create(ActorInitializer init) { return new ShroudRenderer(init.world, this); } + } + + public class ShroudRenderer : IRenderShroud, IWorldLoaded + { + struct ShroudTile + { + public CPos Position; + public float2 ScreenPosition; + public int Variant; + + public Sprite Fog; + public Sprite Shroud; + } + + Sprite[] sprites; + Sprite unexploredTile; + int[] spriteMap; + + ShroudTile[] tiles; + int tileStride, variantStride; + + int shroudHash; + PaletteReference fogPalette, shroudPalette; + Rectangle bounds; + bool useExtendedIndex; + + public ShroudRenderer(World world, ShroudRendererInfo info) + { + var map = world.Map; + bounds = map.Bounds; + useExtendedIndex = info.UseExtendedIndex; + + tiles = new ShroudTile[map.MapSize.X * map.MapSize.Y]; + tileStride = map.MapSize.X; + + // Force update on first render + shroudHash = -1; + + // Load sprite variants + sprites = new Sprite[info.Variants.Length * info.Index.Length]; + variantStride = info.Index.Length; + for (var j = 0; j < info.Variants.Length; j++) + { + var seq = SequenceProvider.GetSequence(info.Sequence, info.Variants[j]); + for (var i = 0; i < info.Index.Length; i++) + sprites[j * variantStride + i] = seq.GetSprite(i); + } + + // Mapping of shrouded directions -> sprite index + spriteMap = new int[useExtendedIndex ? 256 : 16]; + for (var i = 0; i < info.Index.Length; i++) + spriteMap[info.Index[i]] = i; + + // Set individual tile variants to reduce tiling + for (var i = 0; i < tiles.Length; i++) + tiles[i].Variant = Game.CosmeticRandom.Next(info.Variants.Length); + + // Synthesize unexplored tile if it isn't defined + if (!info.Index.Contains(0)) + { + var size = new Size(Game.modData.Manifest.TileSize, Game.modData.Manifest.TileSize); + var data = Exts.MakeArray(size.Width * size.Height, _ => (byte)info.ShroudColor); + var s = Game.modData.SheetBuilder.Add(data, size); + unexploredTile = new Sprite(s.sheet, s.bounds, s.offset, s.channel, info.ShroudBlend); + } + else + unexploredTile = sprites[spriteMap[0]]; + } + + static int FoggedEdges(Shroud s, CPos p, bool useExtendedIndex) + { + if (!s.IsVisible(p.X, p.Y)) + return 15; + + // If a side is shrouded then we also count the corners + var u = 0; + if (!s.IsVisible(p.X, p.Y - 1)) u |= 0x13; + if (!s.IsVisible(p.X + 1, p.Y)) u |= 0x26; + if (!s.IsVisible(p.X, p.Y + 1)) u |= 0x4C; + if (!s.IsVisible(p.X - 1, p.Y)) u |= 0x89; + + var uside = u & 0x0F; + if (!s.IsVisible(p.X - 1, p.Y - 1)) u |= 0x01; + if (!s.IsVisible(p.X + 1, p.Y - 1)) u |= 0x02; + if (!s.IsVisible(p.X + 1, p.Y + 1)) u |= 0x04; + if (!s.IsVisible(p.X - 1, p.Y + 1)) u |= 0x08; + + // RA provides a set of frames for tiles with shrouded + // corners but unshrouded edges. We want to detect this + // situation without breaking the edge -> corner enabling + // in other combinations. The XOR turns off the corner + // bits that are enabled twice, which gives the behavior + // we want here. + return useExtendedIndex ? u ^ uside : u & 0x0F; + } + + static int ShroudedEdges(Shroud s, CPos p, bool useExtendedIndex) + { + if (!s.IsExplored(p.X, p.Y)) + return 15; + + // If a side is shrouded then we also count the corners + var u = 0; + if (!s.IsExplored(p.X, p.Y - 1)) u |= 0x13; + if (!s.IsExplored(p.X + 1, p.Y)) u |= 0x26; + if (!s.IsExplored(p.X, p.Y + 1)) u |= 0x4C; + if (!s.IsExplored(p.X - 1, p.Y)) u |= 0x89; + + var uside = u & 0x0F; + if (!s.IsExplored(p.X - 1, p.Y - 1)) u |= 0x01; + if (!s.IsExplored(p.X + 1, p.Y - 1)) u |= 0x02; + if (!s.IsExplored(p.X + 1, p.Y + 1)) u |= 0x04; + if (!s.IsExplored(p.X - 1, p.Y + 1)) u |= 0x08; + + // RA provides a set of frames for tiles with shrouded + // corners but unshrouded edges. We want to detect this + // situation without breaking the edge -> corner enabling + // in other combinations. The XOR turns off the corner + // bits that are enabled twice, which gives the behavior + // we want here. + return useExtendedIndex ? u ^ uside : u & 0x0F; + } + + static int ObserverShroudedEdges(CPos p, Rectangle bounds, bool useExtendedIndex) + { + var u = 0; + if (p.Y == bounds.Top) u |= 0x13; + if (p.X == bounds.Right - 1) u |= 0x26; + if (p.Y == bounds.Bottom - 1) u |= 0x4C; + if (p.X == bounds.Left) u |= 0x89; + + var uside = u & 0x0F; + if (p.X == bounds.Left && p.Y == bounds.Top) u |= 0x01; + if (p.X == bounds.Right - 1 && p.Y == bounds.Top) u |= 0x02; + if (p.X == bounds.Right - 1 && p.Y == bounds.Bottom - 1) u |= 0x04; + if (p.X == bounds.Left && p.Y == bounds.Bottom - 1) u |= 0x08; + + return useExtendedIndex ? u ^ uside : u & 0x0F; + } + + public void WorldLoaded(World w, WorldRenderer wr) + { + // Cache the tile positions to avoid unnecessary calculations + for (var i = bounds.Left; i < bounds.Right; i++) + { + for (var j = bounds.Top; j < bounds.Bottom; j++) + { + var k = j * tileStride + i; + tiles[k].Position = new CPos(i, j); + tiles[k].ScreenPosition = wr.ScreenPosition(tiles[k].Position.CenterPosition); + } + } + + if (w.LobbyInfo.GlobalSettings.Fog) + fogPalette = wr.Palette("fog"); + + shroudPalette = wr.Palette("shroud"); + } + + Sprite GetTile(int flags, int variant) + { + if (flags == 0) + return null; + + if (flags == 15) + return unexploredTile; + + return sprites[variant * variantStride + spriteMap[flags]]; + } + + void Update(Shroud shroud) + { + var hash = shroud != null ? shroud.Hash : 0; + if (shroudHash == hash) + return; + + shroudHash = hash; + if (shroud == null) + { + // Players with no shroud see the whole map so we only need to set the edges + for (var k = 0; k < tiles.Length; k++) + { + var shrouded = ObserverShroudedEdges(tiles[k].Position, bounds, useExtendedIndex); + tiles[k].Shroud = GetTile(shrouded, tiles[k].Variant); + tiles[k].Fog = GetTile(shrouded, tiles[k].Variant); + } + } + else + { + for (var k = 0; k < tiles.Length; k++) + { + var shrouded = ShroudedEdges(shroud, tiles[k].Position, useExtendedIndex); + var fogged = FoggedEdges(shroud, tiles[k].Position, useExtendedIndex); + + tiles[k].Shroud = GetTile(shrouded, tiles[k].Variant); + tiles[k].Fog = GetTile(fogged, tiles[k].Variant); + } + } + } + + public void RenderShroud(WorldRenderer wr, Shroud shroud) + { + Update(shroud); + + var clip = wr.Viewport.CellBounds; + var width = clip.Width; + for (var j = clip.Top; j < clip.Bottom; j++) + { + var start = j * tileStride + clip.Left; + for (var k = 0; k < width; k++) + { + var s = tiles[start + k].Shroud; + var f = tiles[start + k].Fog; + + if (s != null) + { + var pos = tiles[start + k].ScreenPosition - 0.5f * s.size; + Game.Renderer.WorldSpriteRenderer.DrawSprite(s, pos, shroudPalette); + } + + if (f != null) + { + var pos = tiles[start + k].ScreenPosition - 0.5f * f.size; + Game.Renderer.WorldSpriteRenderer.DrawSprite(f, pos, fogPalette); + } + } + } + } + } +} diff --git a/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs b/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs index ad5805f44f..5d49dccfa4 100755 --- a/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs +++ b/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs @@ -177,6 +177,12 @@ namespace OpenRA.Renderer.Sdl2 ErrorHandler.CheckGlError(); Gl.glBlendEquation(Gl.GL_FUNC_REVERSE_SUBTRACT); break; + case BlendMode.Multiply: + Gl.glEnable(Gl.GL_BLEND); + ErrorHandler.CheckGlError(); + Gl.glBlendFuncSeparate(Gl.GL_DST_COLOR, Gl.GL_ZERO, Gl.GL_ONE, Gl.GL_ONE_MINUS_SRC_ALPHA); + ErrorHandler.CheckGlError(); + break; } ErrorHandler.CheckGlError(); diff --git a/OpenRA.Renderer.SdlCommon/SdlGraphics.cs b/OpenRA.Renderer.SdlCommon/SdlGraphics.cs index 8c8de209e3..ec87e0f8d9 100644 --- a/OpenRA.Renderer.SdlCommon/SdlGraphics.cs +++ b/OpenRA.Renderer.SdlCommon/SdlGraphics.cs @@ -179,6 +179,12 @@ namespace OpenRA.Renderer.SdlCommon ErrorHandler.CheckGlError(); Gl.glBlendEquation(Gl.GL_FUNC_REVERSE_SUBTRACT); break; + case BlendMode.Multiply: + Gl.glEnable(Gl.GL_BLEND); + ErrorHandler.CheckGlError(); + Gl.glBlendFuncSeparate(Gl.GL_DST_COLOR, Gl.GL_ZERO, Gl.GL_ONE, Gl.GL_ONE_MINUS_SRC_ALPHA); + ErrorHandler.CheckGlError(); + break; } ErrorHandler.CheckGlError(); } diff --git a/mods/cnc/bits/shadow.shp b/mods/cnc/bits/shadow.shp deleted file mode 100644 index 8d7d7e76ca..0000000000 Binary files a/mods/cnc/bits/shadow.shp and /dev/null differ diff --git a/mods/cnc/rules/system.yaml b/mods/cnc/rules/system.yaml index b12ddf9188..ca3a0f8a1a 100644 --- a/mods/cnc/rules/system.yaml +++ b/mods/cnc/rules/system.yaml @@ -273,10 +273,9 @@ World: Type: Shroud ShroudPalette@fog: Name: fog - Type: Fog - ShroudPalette@combined: - Name: shroudfog - Type: Combined + Fog: true + ShroudRenderer: + Variants: typea, typeb, typec, typed Country@gdi: Name: GDI Race: gdi diff --git a/mods/cnc/sequences/misc.yaml b/mods/cnc/sequences/misc.yaml index 06dbc7546a..ad893668bd 100644 --- a/mods/cnc/sequences/misc.yaml +++ b/mods/cnc/sequences/misc.yaml @@ -360,4 +360,18 @@ resources: bti11: bti11 Length: * bti12: bti12 - Length: * \ No newline at end of file + Length: * + +shroud: + typea: shadow + Start: 0 + Length: 12 + typeb: shadow + Start: 12 + Length: 12 + typec: shadow + Start: 24 + Length: 12 + typed: shadow + Start: 36 + Length: 12 \ No newline at end of file diff --git a/mods/d2k/bits/shadow.shp b/mods/d2k/bits/shadow.shp deleted file mode 100644 index 286da4a8f5..0000000000 Binary files a/mods/d2k/bits/shadow.shp and /dev/null differ diff --git a/mods/d2k/rules/system.yaml b/mods/d2k/rules/system.yaml index 2dcf67d748..1079401928 100644 --- a/mods/d2k/rules/system.yaml +++ b/mods/d2k/rules/system.yaml @@ -414,21 +414,28 @@ World: Name: moveflash Filename: DATA.R8 Offset: 2572352 - A: 64 + InvertColor: true PaletteFromRGBA@disabled: Name: disabled R: 0 G: 0 B: 0 A: 180 - ShroudPalette@shroud: - Type: Shroud - ShroudPalette@fog: + PaletteFromR8@shroud: + Name: shroud + Filename: DATA.R8 + Offset: 12007 + InvertColor: true + FogPaletteFromR8@fog: Name: fog - Type: Fog - ShroudPalette@combined: - Name: shroudfog - Type: Combined + Filename: DATA.R8 + Offset: 12007 + InvertColor: true + ShroudRenderer: + Variants: typea, typeb, typec, typed + Index: 11, 3, 7, 9, 6, 13, 12, 14, 4, 8, 2, 1, 5, 10 + ShroudColor: 31 + ShroudBlend: Multiply Country@Atreides: Name: Atreides Race: atreides diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index 5774d04056..533476230d 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -304,10 +304,32 @@ moveflsh: Start: 3621 Length: 5 Tick: 80 - BlendMode: Subtractive + BlendMode: Multiply resources: spice: BLOXBASE Frames: 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 300, 301, 320, 321 Length: 54 Offset: -16,-16 + +shroud: + typea: DATA + Start: 40 + Length: 14 + Offset: -16,-16 + BlendMode: Multiply + typeb: DATA + Start: 56 + Length: 14 + Offset: -16,-16 + BlendMode: Multiply + typec: DATA + Start: 72 + Length: 14 + Offset: -16,-16 + BlendMode: Multiply + typed: DATA + Start: 88 + Length: 14 + Offset: -16,-16 + BlendMode: Multiply diff --git a/mods/ra/rules/system.yaml b/mods/ra/rules/system.yaml index 2e3af5b920..d125c58b59 100644 --- a/mods/ra/rules/system.yaml +++ b/mods/ra/rules/system.yaml @@ -616,10 +616,10 @@ World: Type: Shroud ShroudPalette@fog: Name: fog - Type: Fog - ShroudPalette@combined: - Name: shroudfog - Type: Combined + Fog: true + ShroudRenderer: + Index: 255, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 20, 40, 56, 65, 97, 130, 148, 194, 24, 33, 66, 132, 28, 41, 67, 134, 1, 2, 4, 8, 3, 6, 12, 9, 7, 14, 13, 11, 5, 10, 15, 255 + UseExtendedIndex: true Country@0: Name: Allies Race: allies diff --git a/mods/ra/sequences/misc.yaml b/mods/ra/sequences/misc.yaml index 3016ebea10..5db38a16db 100644 --- a/mods/ra/sequences/misc.yaml +++ b/mods/ra/sequences/misc.yaml @@ -486,4 +486,8 @@ resources: gem03: gem03 Length: * gem04: gem04 + Length: * + +shroud: + shroud: shadow Length: * \ No newline at end of file diff --git a/mods/ts/rules/system.yaml b/mods/ts/rules/system.yaml index cc14e72d5e..81e8c48519 100644 --- a/mods/ts/rules/system.yaml +++ b/mods/ts/rules/system.yaml @@ -101,10 +101,10 @@ World: Type: Shroud ShroudPalette@fog: Name: fog - Type: Fog - ShroudPalette@combined: - Name: shroudfog - Type: Combined + Fog: true + ShroudRenderer: + Index: 255, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 20, 40, 56, 65, 97, 130, 148, 194, 24, 33, 66, 132, 28, 41, 67, 134, 1, 2, 4, 8, 3, 6, 12, 9, 7, 14, 13, 11, 5, 10, 15, 255 + UseExtendedIndex: true VoxelNormalsPalette@normals: Name: normals Type: TiberianSun diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index b14c3c5006..d0b62f2f01 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -253,4 +253,8 @@ moveflsh: # TODO: placeholder resources: fake: shadow + Length: * + +shroud: + shroud: shadow Length: * \ No newline at end of file