From 303762eb31a16ca6a4c0d0e58bae3ee5f80eb6e4 Mon Sep 17 00:00:00 2001 From: Nyerguds Date: Wed, 31 May 2023 23:51:28 +0200 Subject: [PATCH] * Progress on classic color management * Added cleanup routines for tilesets and object type previews. --- CnCTDRAMapEditor/CnCTDRAMapEditor.csproj | 2 + CnCTDRAMapEditor/Interface/IBrowsableType.cs | 3 + CnCTDRAMapEditor/Interface/ITeamColor.cs | 2 - .../Interface/ITeamColorManager.cs | 7 +- CnCTDRAMapEditor/MainForm.cs | 54 +++---- CnCTDRAMapEditor/Model/BuildingType.cs | 93 ++++++------ CnCTDRAMapEditor/Model/InfantryType.cs | 10 ++ CnCTDRAMapEditor/Model/Map.cs | 41 ++++++ CnCTDRAMapEditor/Model/OverlayType.cs | 10 ++ CnCTDRAMapEditor/Model/SmudgeType.cs | 10 ++ CnCTDRAMapEditor/Model/TemplateType.cs | 10 ++ CnCTDRAMapEditor/Model/TerrainType.cs | 10 ++ CnCTDRAMapEditor/Model/UnitType.cs | 10 ++ CnCTDRAMapEditor/RedAlert/GamePluginRA.cs | 5 +- CnCTDRAMapEditor/Render/MapRenderer.cs | 4 +- CnCTDRAMapEditor/TiberianDawn/GamePluginTD.cs | 9 +- CnCTDRAMapEditor/Utility/TeamColor.cs | 7 +- CnCTDRAMapEditor/Utility/TeamColorManager.cs | 14 +- CnCTDRAMapEditor/Utility/TeamRemap.cs | 56 +++++++ CnCTDRAMapEditor/Utility/TeamRemapManager.cs | 137 ++++++++++++++++++ CnCTDRAMapEditor/Utility/TextureManager.cs | 117 ++------------- CnCTDRAMapEditor/Utility/Tileset.cs | 31 +++- 22 files changed, 450 insertions(+), 192 deletions(-) create mode 100644 CnCTDRAMapEditor/Utility/TeamRemap.cs create mode 100644 CnCTDRAMapEditor/Utility/TeamRemapManager.cs diff --git a/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj b/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj index 12a16a1..a1efe9d 100644 --- a/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj +++ b/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj @@ -618,6 +618,8 @@ + + diff --git a/CnCTDRAMapEditor/Interface/IBrowsableType.cs b/CnCTDRAMapEditor/Interface/IBrowsableType.cs index ce61bec..b5ee317 100644 --- a/CnCTDRAMapEditor/Interface/IBrowsableType.cs +++ b/CnCTDRAMapEditor/Interface/IBrowsableType.cs @@ -12,6 +12,8 @@ // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection +using MobiusEditor.Model; +using System; using System.Drawing; namespace MobiusEditor.Interface @@ -20,5 +22,6 @@ namespace MobiusEditor.Interface { string DisplayName { get; } Bitmap Thumbnail { get; } + void Reset(); } } diff --git a/CnCTDRAMapEditor/Interface/ITeamColor.cs b/CnCTDRAMapEditor/Interface/ITeamColor.cs index 42442bb..e13e3b8 100644 --- a/CnCTDRAMapEditor/Interface/ITeamColor.cs +++ b/CnCTDRAMapEditor/Interface/ITeamColor.cs @@ -14,8 +14,6 @@ namespace MobiusEditor.Interface { /// Name of the color, used as identifier. string Name { get; } - /// A general color representing this team color. - Color BaseColor { get; } /// /// Apply this color to a given image. diff --git a/CnCTDRAMapEditor/Interface/ITeamColorManager.cs b/CnCTDRAMapEditor/Interface/ITeamColorManager.cs index a0b4c87..701f304 100644 --- a/CnCTDRAMapEditor/Interface/ITeamColorManager.cs +++ b/CnCTDRAMapEditor/Interface/ITeamColorManager.cs @@ -15,7 +15,12 @@ namespace MobiusEditor.Interface Color RemapBaseColor { get; } ITeamColor this[string key] { get; } + /// Gets a general color representing this team color. + /// Color key + /// The basic color for this team color. + Color GetBaseColor(string key); + void Load(string path); - void Reset(GameType gameType); + void Reset(GameType gameType, string theater); } } \ No newline at end of file diff --git a/CnCTDRAMapEditor/MainForm.cs b/CnCTDRAMapEditor/MainForm.cs index 75c6e37..eb64871 100644 --- a/CnCTDRAMapEditor/MainForm.cs +++ b/CnCTDRAMapEditor/MainForm.cs @@ -1059,7 +1059,7 @@ namespace MobiusEditor { var fileInfo = new FileInfo(fileName); String name = fileInfo.FullName; - if (!IdentifyMap(name, out FileType fileType, out GameType gameType, out bool isTdMegaMap)) + if (!IdentifyMap(name, out FileType fileType, out GameType gameType, out bool isTdMegaMap, out string theater)) { string extension = Path.GetExtension(name).TrimStart('.'); // No point in supporting jpeg here; the mapping needs distinct colours without fades. @@ -1092,7 +1092,7 @@ namespace MobiusEditor ModPaths.TryGetValue(gameType, out modPaths); } loadMultiThreader.ExecuteThreaded( - () => LoadFile(name, fileType, gameType, isTdMegaMap, modPaths), + () => LoadFile(name, fileType, gameType, theater, isTdMegaMap, modPaths), PostLoad, true, (e,l) => LoadUnloadUi(e, l, loadMultiThreader), "Loading map"); @@ -1138,10 +1138,11 @@ namespace MobiusEditor "Saving map"); } - private Boolean IdentifyMap(String loadFilename, out FileType fileType, out GameType gameType, out bool isTdMegaMap) + private Boolean IdentifyMap(String loadFilename, out FileType fileType, out GameType gameType, out bool isTdMegaMap, out string theater) { fileType = FileType.None; gameType = GameType.None; + theater = null; isTdMegaMap = false; try { @@ -1212,6 +1213,7 @@ namespace MobiusEditor { return false; } + theater = (iniContents["Map"].TryGetValue("Theater") ?? "temperate").ToLower(); switch (fileType) { case FileType.INI: @@ -1308,20 +1310,20 @@ namespace MobiusEditor } } - private static IGamePlugin LoadNewPlugin(GameType gameType, bool isTdMegaMap, string[] modPaths) + private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, string[] modPaths) { - return LoadNewPlugin(gameType, isTdMegaMap, modPaths, false); + return LoadNewPlugin(gameType, theater, isTdMegaMap, modPaths, false); } - private static IGamePlugin LoadNewPlugin(GameType gameType, bool isTdMegaMap, string[] modPaths, bool noImage) + private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, string[] modPaths, bool noImage) { // Resetting to a specific game type will take care of classic mode. Globals.TheArchiveManager.ExpandModPaths = modPaths; Globals.TheArchiveManager.Reset(gameType); Globals.TheGameTextManager.Reset(gameType); - Globals.TheTextureManager.Reset(); + Globals.TheTextureManager.Reset(gameType, theater); Globals.TheTilesetManager.Reset(); - Globals.TheTeamColorManager.Reset(gameType); + Globals.TheTeamColorManager.Reset(gameType, theater); IGamePlugin plugin = null; if (gameType == GameType.TiberianDawn) { @@ -1334,6 +1336,7 @@ namespace MobiusEditor else if (gameType == GameType.RedAlert) { Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML"); + Globals.TheTeamColorManager.Load("palette.cps"); plugin = new RedAlert.GamePluginRA(!noImage); } else if (gameType == GameType.SoleSurvivor) @@ -1358,35 +1361,26 @@ namespace MobiusEditor new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(61, 61, 59)); tcm.AddTeamColor(teamColorNone); } - else // if (teamColorManager is TeamRemapManager tcc) - { - // TODO classic team color - //TeamRemap teamColorNone = new TeamRemap(tcc); - //teamColorNone.Load(/*TODO*/); - //tcc.AddTeamColor(teamColorNone); - } } private static void AddTeamColorPurple(ITeamColorManager teamColorManager) { if (teamColorManager is TeamColorManager tcm) { - // Add extra purple for flag.. - TeamColor teamColorPurple = new TeamColor(tcm); - teamColorPurple.Load("PURPLE", "BASE_TEAM", + // Add extra colors for flags. + TeamColor teamColorSeven = new TeamColor(tcm); + teamColorSeven.Load(tcm.GetItem("BAD_UNIT"), "MULTI7"); + tcm.AddTeamColor(teamColorSeven); + + TeamColor teamColorEight = new TeamColor(tcm); + teamColorEight.Load("MULTI8", "BASE_TEAM", Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0, new Vector3(0.410f, 0.300f, 0.000f), new Vector3(0f, 1f, 1f), new Vector2(0.0f, 1.0f), new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(77, 13, 255)); - tcm.AddTeamColor(teamColorPurple); - } - else // if (teamColorManager is TeamRemapManager tcc) - { - // TODO classic team color - //TeamRemap teamColorPurple = new TeamRemap(tcc); - //teamColorPurple.Load(/*TODO*/); - //tcc.AddTeamColor(teamColorPurple); + tcm.AddTeamColor(teamColorEight); } } + /// /// The separate-threaded part for making a new map. /// @@ -1424,7 +1418,7 @@ namespace MobiusEditor } try { - IGamePlugin plugin = LoadNewPlugin(gameType, isTdMegaMap, modPaths); + IGamePlugin plugin = LoadNewPlugin(gameType, theater, isTdMegaMap, modPaths); // This initialises the theater plugin.New(theater); if (SteamworksUGC.IsInit) @@ -1488,11 +1482,11 @@ namespace MobiusEditor /// /// /// - private static MapLoadInfo LoadFile(string loadFilename, FileType fileType, GameType gameType, bool isTdMegaMap, string[] modPaths) + private static MapLoadInfo LoadFile(string loadFilename, FileType fileType, GameType gameType, string theater, bool isTdMegaMap, string[] modPaths) { try { - IGamePlugin plugin = LoadNewPlugin(gameType, isTdMegaMap, modPaths); + IGamePlugin plugin = LoadNewPlugin(gameType, theater, isTdMegaMap, modPaths); string[] errors = plugin.Load(loadFilename, fileType).ToArray(); return new MapLoadInfo(loadFilename, fileType, plugin, errors); } @@ -1650,7 +1644,7 @@ namespace MobiusEditor } // Unload graphics Globals.TheTilesetManager.Reset(); - Globals.TheTextureManager.Reset(); + Globals.TheTextureManager.Reset(GameType.None, null); // Clean up loaded file status filename = null; loadedFileType = FileType.None; diff --git a/CnCTDRAMapEditor/Model/BuildingType.cs b/CnCTDRAMapEditor/Model/BuildingType.cs index 5796b2f..14bc349 100644 --- a/CnCTDRAMapEditor/Model/BuildingType.cs +++ b/CnCTDRAMapEditor/Model/BuildingType.cs @@ -64,7 +64,7 @@ namespace MobiusEditor.Model public int Storage { get; set; } - public Rectangle OverlapBounds => new Rectangle(Point.Empty, new Size(OccupyMask.GetLength(1), OccupyMask.GetLength(0))); + public Rectangle OverlapBounds => new Rectangle(Point.Empty, new Size(this.OccupyMask.GetLength(1), this.OccupyMask.GetLength(0))); public bool[,] OpaqueMask { get; private set; } public bool[,] OccupyMask { get; private set; } @@ -76,19 +76,19 @@ namespace MobiusEditor.Model public bool HasBib { - get { return (Flag & BuildingTypeFlag.Bib) == BuildingTypeFlag.Bib; } + get { return (this.Flag & BuildingTypeFlag.Bib) == BuildingTypeFlag.Bib; } set { // Bibs are only supported for widths 2 to 4 - if (value && Size.Width >= 2 && Size.Width <= 4) + if (value && this.Size.Width >= 2 && this.Size.Width <= 4) { - Flag |= BuildingTypeFlag.Bib; + this.Flag |= BuildingTypeFlag.Bib; } else { - Flag &= ~BuildingTypeFlag.Bib; + this.Flag &= ~BuildingTypeFlag.Bib; } - RecalculateBibs(); + this.RecalculateBibs(); } } @@ -101,14 +101,14 @@ namespace MobiusEditor.Model public bool IsAircraft => false; public bool IsFixedWing => false; - public bool IsFake => (Flag & BuildingTypeFlag.Fake) == BuildingTypeFlag.Fake; - public bool HasTurret => (Flag & BuildingTypeFlag.Turret) == BuildingTypeFlag.Turret; - public bool IsSingleFrame => (Flag & BuildingTypeFlag.SingleFrame) == BuildingTypeFlag.SingleFrame; - public bool CanRemap => (Flag & BuildingTypeFlag.NoRemap) != BuildingTypeFlag.NoRemap; + public bool IsFake => (this.Flag & BuildingTypeFlag.Fake) == BuildingTypeFlag.Fake; + public bool HasTurret => (this.Flag & BuildingTypeFlag.Turret) == BuildingTypeFlag.Turret; + public bool IsSingleFrame => (this.Flag & BuildingTypeFlag.SingleFrame) == BuildingTypeFlag.SingleFrame; + public bool CanRemap => (this.Flag & BuildingTypeFlag.NoRemap) != BuildingTypeFlag.NoRemap; /// /// Indicates buildings that have pieces sticking out at the top that should not overlap the objects on these cells. /// - public bool IsFlat => (Flag & BuildingTypeFlag.Flat) == BuildingTypeFlag.Flat; + public bool IsFlat => (this.Flag & BuildingTypeFlag.Flat) == BuildingTypeFlag.Flat; public BuildingType(sbyte id, string name, string textId, int powerProd, int powerUse, int storage, int width, int height, string occupyMask, string ownerHouse, TheaterType[] theaters, string factoryOverlay, int frameOffset, String graphicsSource, BuildingTypeFlag flag) { @@ -118,7 +118,7 @@ namespace MobiusEditor.Model this.Name = name; this.GraphicsSource = graphicsSource ?? name; this.DisplayName = !String.IsNullOrEmpty(textId) && !String.IsNullOrEmpty(Globals.TheGameTextManager[textId]) - ? Globals.TheGameTextManager[textId] + " (" + Name.ToUpperInvariant() + ")" + ? Globals.TheGameTextManager[textId] + " (" + this.Name.ToUpperInvariant() + ")" : name.ToUpperInvariant(); this.PowerProduction = powerProd; this.PowerUsage = powerUse; @@ -184,30 +184,30 @@ namespace MobiusEditor.Model private void RecalculateBibs() { - int maskY = BaseOccupyMask.GetLength(0); - int maskX = BaseOccupyMask.GetLength(1); - if (HasBib) + int maskY = this.BaseOccupyMask.GetLength(0); + int maskX = this.BaseOccupyMask.GetLength(1); + if (this.HasBib) { - OccupyMask = new bool[maskY + 1, maskX]; - for (var y = 0; y < maskY; ++y) + this.OccupyMask = new bool[maskY + 1, maskX]; + for (int y = 0; y < maskY; ++y) { - for (var x = 0; x < maskX; ++x) + for (int x = 0; x < maskX; ++x) { - OccupyMask[y, x] = BaseOccupyMask[y, x]; + this.OccupyMask[y, x] = this.BaseOccupyMask[y, x]; } } if (Globals.BlockingBibs) { - for (var x = 0; x < maskX; ++x) + for (int x = 0; x < maskX; ++x) { - OccupyMask[maskY, x] = true; - OccupyMask[maskY - 1, x] = true; + this.OccupyMask[maskY, x] = true; + this.OccupyMask[maskY - 1, x] = true; } } } else { - OccupyMask = BaseOccupyMask; + this.OccupyMask = this.BaseOccupyMask; } } @@ -218,15 +218,15 @@ namespace MobiusEditor.Model int baseMaskX = this.BaseOccupyMask.GetLength(1); string occupyMask = GeneralUtils.GetStringFromMask(this.BaseOccupyMask); TheaterType[] theaters = null; - if (Theaters != null) + if (this.Theaters != null) { - int thLen = Theaters.Length; + int thLen = this.Theaters.Length; theaters = new TheaterType[thLen]; - Array.Copy(Theaters, theaters, thLen); + Array.Copy(this.Theaters, theaters, thLen); } // Don't do lookup of the UI name. We don't have the original lookup ID at this point, so don't bother, and just restore the name afterwards. - BuildingType newBld = new BuildingType(ID, Name, null, PowerProduction, PowerUsage, Storage, baseMaskX, baseMaskY, occupyMask, OwnerHouse, theaters, FactoryOverlay, FrameOFfset, GraphicsSource, Flag); - newBld.DisplayName = DisplayName; + BuildingType newBld = new BuildingType(this.ID, this.Name, null, this.PowerProduction, this.PowerUsage, this.Storage, baseMaskX, baseMaskY, occupyMask, this.OwnerHouse, theaters, this.FactoryOverlay, this.FrameOFfset, this.GraphicsSource, this.Flag); + newBld.DisplayName = this.DisplayName; return newBld; } @@ -238,11 +238,11 @@ namespace MobiusEditor.Model } else if (obj is sbyte) { - return ID == (sbyte)obj; + return this.ID == (sbyte)obj; } else if (obj is string) { - return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase); + return string.Equals(this.Name, obj as string, StringComparison.OrdinalIgnoreCase); } return base.Equals(obj); @@ -250,44 +250,44 @@ namespace MobiusEditor.Model public override int GetHashCode() { - return ID.GetHashCode(); + return this.ID.GetHashCode(); } public override string ToString() { - return (Name ?? String.Empty).ToUpperInvariant(); + return (this.Name ?? String.Empty).ToUpperInvariant(); } public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction) { - var oldImage = Thumbnail; - var mockBuilding = new Building() + Bitmap oldImage = this.Thumbnail; + Building mockBuilding = new Building() { Type = this, House = house, Strength = 256, Direction = direction }; - var render = MapRenderer.RenderBuilding(gameType, theater, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding); + (Rectangle, Action, bool) render = MapRenderer.RenderBuilding(gameType, theater, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding); if (!render.Item1.IsEmpty) { - var th = new Bitmap(render.Item1.Width, render.Item1.Height); + Bitmap th = new Bitmap(render.Item1.Width, render.Item1.Height); th.SetResolution(96, 96); - using (var g = Graphics.FromImage(th)) + using (Graphics g = Graphics.FromImage(th)) { MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale); render.Item2(g); - if (IsFake) + if (this.IsFake) { MapRenderer.RenderFakeBuildingLabel(g, mockBuilding, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, false); } } - Thumbnail = th; - OpaqueMask = GeneralUtils.FindOpaqueCells(th, Size, 10, 25, 0x80); + this.Thumbnail = th; + this.OpaqueMask = GeneralUtils.FindOpaqueCells(th, this.Size, 10, 25, 0x80); } else { - Thumbnail = null; + this.Thumbnail = null; } if (oldImage != null) { @@ -295,5 +295,16 @@ namespace MobiusEditor.Model catch { /* ignore */ } } } + + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/Model/InfantryType.cs b/CnCTDRAMapEditor/Model/InfantryType.cs index 6b01f29..938de3a 100644 --- a/CnCTDRAMapEditor/Model/InfantryType.cs +++ b/CnCTDRAMapEditor/Model/InfantryType.cs @@ -122,5 +122,15 @@ namespace MobiusEditor.Model catch { /* ignore */ } } } + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/Model/Map.cs b/CnCTDRAMapEditor/Model/Map.cs index c2104be..5494ddf 100644 --- a/CnCTDRAMapEditor/Model/Map.cs +++ b/CnCTDRAMapEditor/Model/Map.cs @@ -1987,5 +1987,46 @@ namespace MobiusEditor.Model } return info; } + + public void ResetCachedGraphics() + { + // Dispose of cached images. This is non-destructive; the type objects themselves + // don't actually get disposed. Their thumbnail simply gets disposed and cleared. + foreach (ITechnoType technoType in this.AllTeamTechnoTypes) + { + // units, boats, aircraft, infantry + technoType.Reset(); + } + // probably not needed since it's in the team techno types. + foreach (UnitType unitType in this.AllUnitTypes) + { + unitType.Reset(); + } + // probably not needed since it's in the team techno types. + foreach (InfantryType infantryType in this.AllInfantryTypes) + { + infantryType.Reset(); + } + foreach (BuildingType buildingType in this.BuildingTypes) + { + buildingType.Reset(); + } + foreach (TemplateType template in this.TemplateTypes) + { + template.Reset(); + } + foreach (TerrainType terrainType in this.TerrainTypes) + { + terrainType.Reset(); + } + foreach (OverlayType overlayType in this.OverlayTypes) + { + overlayType.Reset(); + } + foreach (SmudgeType smudgeType in this.SmudgeTypes) + { + smudgeType.Reset(); + } + } } } diff --git a/CnCTDRAMapEditor/Model/OverlayType.cs b/CnCTDRAMapEditor/Model/OverlayType.cs index 4329892..d978647 100644 --- a/CnCTDRAMapEditor/Model/OverlayType.cs +++ b/CnCTDRAMapEditor/Model/OverlayType.cs @@ -191,5 +191,15 @@ namespace MobiusEditor.Model catch { /* ignore */ } } } + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/Model/SmudgeType.cs b/CnCTDRAMapEditor/Model/SmudgeType.cs index 70005a7..121e2af 100644 --- a/CnCTDRAMapEditor/Model/SmudgeType.cs +++ b/CnCTDRAMapEditor/Model/SmudgeType.cs @@ -196,5 +196,15 @@ namespace MobiusEditor.Model return null; } } + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/Model/TemplateType.cs b/CnCTDRAMapEditor/Model/TemplateType.cs index daf45af..2fe4687 100644 --- a/CnCTDRAMapEditor/Model/TemplateType.cs +++ b/CnCTDRAMapEditor/Model/TemplateType.cs @@ -484,5 +484,15 @@ namespace MobiusEditor.Model // Should never happen. return new Point(0, 0); } + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/Model/TerrainType.cs b/CnCTDRAMapEditor/Model/TerrainType.cs index e2d9f1d..51d3805 100644 --- a/CnCTDRAMapEditor/Model/TerrainType.cs +++ b/CnCTDRAMapEditor/Model/TerrainType.cs @@ -219,5 +219,15 @@ namespace MobiusEditor.Model catch { /* ignore */ } } } + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/Model/UnitType.cs b/CnCTDRAMapEditor/Model/UnitType.cs index 4f11965..064dffb 100644 --- a/CnCTDRAMapEditor/Model/UnitType.cs +++ b/CnCTDRAMapEditor/Model/UnitType.cs @@ -184,5 +184,15 @@ namespace MobiusEditor.Model catch { /* ignore */ } } } + public void Reset() + { + Bitmap oldImage = this.Thumbnail; + this.Thumbnail = null; + if (oldImage != null) + { + try { oldImage.Dispose(); } + catch { /* ignore */ } + } + } } } diff --git a/CnCTDRAMapEditor/RedAlert/GamePluginRA.cs b/CnCTDRAMapEditor/RedAlert/GamePluginRA.cs index f58d33d..29d7fd1 100644 --- a/CnCTDRAMapEditor/RedAlert/GamePluginRA.cs +++ b/CnCTDRAMapEditor/RedAlert/GamePluginRA.cs @@ -3803,7 +3803,10 @@ namespace MobiusEditor.RedAlert { if (disposing) { - MapImage?.Dispose(); + try { MapImage?.Dispose(); } + catch { /* ignore */ } + // Dispose of cached images in type objects. This is non-destructive; the type objects themselves don't actually get disposed. + Map.ResetCachedGraphics(); } disposedValue = true; } diff --git a/CnCTDRAMapEditor/Render/MapRenderer.cs b/CnCTDRAMapEditor/Render/MapRenderer.cs index 098292e..f3f7352 100644 --- a/CnCTDRAMapEditor/Render/MapRenderer.cs +++ b/CnCTDRAMapEditor/Render/MapRenderer.cs @@ -1573,7 +1573,7 @@ namespace MobiusEditor.Render return; } ITeamColor tc = Globals.TheTeamColorManager[building.House.BuildingTeamColor]; - Color circleColor = tc?.BaseColor ?? Globals.TheTeamColorManager.RemapBaseColor; + Color circleColor = Globals.TheTeamColorManager.GetBaseColor(tc.Name); bool[,] cells = building.Type.BaseOccupyMask; int maskY = cells.GetLength(0); int maskX = cells.GetLength(1); @@ -1601,7 +1601,7 @@ namespace MobiusEditor.Render return; } ITeamColor tc = Globals.TheTeamColorManager[unit.House.BuildingTeamColor]; - Color circleColor = tc?.BaseColor ?? Globals.TheTeamColorManager.RemapBaseColor; + Color circleColor = Globals.TheTeamColorManager.GetBaseColor(tc.Name); Color alphacorr = Color.FromArgb(unit.Tint.A * 128 / 256, circleColor); if (isJammer) { diff --git a/CnCTDRAMapEditor/TiberianDawn/GamePluginTD.cs b/CnCTDRAMapEditor/TiberianDawn/GamePluginTD.cs index 63cf448..f9c28af 100644 --- a/CnCTDRAMapEditor/TiberianDawn/GamePluginTD.cs +++ b/CnCTDRAMapEditor/TiberianDawn/GamePluginTD.cs @@ -394,9 +394,9 @@ namespace MobiusEditor.TiberianDawn flagColors[mpId] = Globals.TheTeamColorManager[house.UnitTeamColor]; } // Metallic light blue - flagColors[6] = Globals.TheTeamColorManager["BAD_UNIT"]; + flagColors[6] = Globals.TheTeamColorManager["MULTI7"]; // RA Purple - flagColors[7] = Globals.TheTeamColorManager["PURPLE"]; + flagColors[7] = Globals.TheTeamColorManager["MULTI8"]; Size mapSize = !megaMap ? Constants.MaxSize : Constants.MaxSizeMega; Map = new Map(basicSection, null, mapSize, typeof(House), houseTypes, flagColors, TheaterTypes.GetTypes(), TemplateTypes.GetTypes(), @@ -3284,7 +3284,10 @@ namespace MobiusEditor.TiberianDawn { if (disposing) { - MapImage?.Dispose(); + try { MapImage?.Dispose(); } + catch { /* ignore */ } + // Dispose of cached images in type objects. This is non-destructive; the type objects themselves don't actually get disposed. + Map.ResetCachedGraphics(); } disposedValue = true; } diff --git a/CnCTDRAMapEditor/Utility/TeamColor.cs b/CnCTDRAMapEditor/Utility/TeamColor.cs index 0f5c6d2..dd7367a 100644 --- a/CnCTDRAMapEditor/Utility/TeamColor.cs +++ b/CnCTDRAMapEditor/Utility/TeamColor.cs @@ -85,14 +85,13 @@ namespace MobiusEditor.Utility public TeamColor(TeamColorManager teamColorManager, TeamColor col, string newName, Vector3 hsvShiftOverride) { this.teamColorManager = teamColorManager; - this.Load(col); - this.Name = newName; + this.Load(col, newName); this.hsvShift = hsvShiftOverride; } - public void Load(TeamColor col) + public void Load(TeamColor col, string newName) { - this.Name = col.Name; + this.Name = newName ?? col.Name; this.Variant = col.Variant; this.lowerBounds = col.LowerBounds; this.upperBounds = col.UpperBounds; diff --git a/CnCTDRAMapEditor/Utility/TeamColorManager.cs b/CnCTDRAMapEditor/Utility/TeamColorManager.cs index 290fc47..2d9d2e6 100644 --- a/CnCTDRAMapEditor/Utility/TeamColorManager.cs +++ b/CnCTDRAMapEditor/Utility/TeamColorManager.cs @@ -28,8 +28,16 @@ namespace MobiusEditor.Utility private readonly IArchiveManager megafileManager; public Color RemapBaseColor => Color.FromArgb(0x00, 0xFF, 0x00); - public ITeamColor this[string key] => !string.IsNullOrEmpty(key) ? (ITeamColor)teamColors[key]: null; + public ITeamColor this[string key] => !string.IsNullOrEmpty(key) && teamColors.ContainsKey(key) ? (ITeamColor)teamColors[key]: null; + public Color GetBaseColor(string key) + { + if (!string.IsNullOrEmpty(key) && teamColors.ContainsKey(key)) + { + return teamColors[key].BaseColor; + } + return RemapBaseColor; + } public TeamColor GetItem(string key) => !string.IsNullOrEmpty(key) ? teamColors[key] : null; public void RemoveTeamColor(string col) @@ -56,12 +64,12 @@ namespace MobiusEditor.Utility } } - public TeamColorManager(IArchiveManager megafileManager, params string[] expandModPaths) + public TeamColorManager(IArchiveManager megafileManager) { this.megafileManager = megafileManager; } - public void Reset(GameType gameType) + public void Reset(GameType gameType, string theater) { teamColors.Clear(); } diff --git a/CnCTDRAMapEditor/Utility/TeamRemap.cs b/CnCTDRAMapEditor/Utility/TeamRemap.cs new file mode 100644 index 0000000..f66ce04 --- /dev/null +++ b/CnCTDRAMapEditor/Utility/TeamRemap.cs @@ -0,0 +1,56 @@ +using MobiusEditor.Interface; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MobiusEditor.Utility +{ + class TeamRemap : ITeamColor + { + public string Name { get; private set; } + public byte UnitRadarColor { get; private set; } + public byte BuildingRadarColor { get; private set; } + + private byte[] remapTable; + + public TeamRemap(string name, byte unitRadarColor, byte buildingRadarColor, byte remapstart, byte[] remapValues) + { + this.Name = name; + this.UnitRadarColor = unitRadarColor; + this.BuildingRadarColor = buildingRadarColor; + this.remapTable = Enumerable.Range(0, 0x100).Cast().ToArray(); + int max = Math.Max(0x100, remapstart + remapValues.Length); + for (int i = 0; i < max; ++i) + { + remapTable[remapstart + i] = remapValues[i]; + } + } + + public TeamRemap(string name, byte unitRadarColor, byte buildingRadarColor, byte[] remapOrigins, byte[] remapValues) + { + this.Name = name; + this.UnitRadarColor = unitRadarColor; + this.BuildingRadarColor = buildingRadarColor; + this.remapTable = Enumerable.Range(0, 0x100).Cast().ToArray(); + int max = Math.Max(remapOrigins.Length, remapValues.Length); + for (int i = 0; i < max; ++i) + { + remapTable[remapOrigins[i]] = remapValues[i]; + } + } + + + public void ApplyToImage(Bitmap image) + { + throw new NotImplementedException(); + } + + public void ApplyToImage(Bitmap image, out Rectangle opaqueBounds) + { + throw new NotImplementedException(); + } + } +} diff --git a/CnCTDRAMapEditor/Utility/TeamRemapManager.cs b/CnCTDRAMapEditor/Utility/TeamRemapManager.cs new file mode 100644 index 0000000..d0eaab9 --- /dev/null +++ b/CnCTDRAMapEditor/Utility/TeamRemapManager.cs @@ -0,0 +1,137 @@ +using MobiusEditor.Interface; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace MobiusEditor.Utility +{ + class TeamRemapManager : ITeamColorManager + { + // TD remap + public static readonly TeamRemap RemapTdGoodGuy = new TeamRemap("GOOD", 176, 180, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 }); + public static readonly TeamRemap RemapTdBadGuyRed = new TeamRemap("BAD_STRUCTURE", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 }); + public static readonly TeamRemap RemapTdBadGuyGray = new TeamRemap("BAD_UNIT", 127, 123, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 }); + // The color name identifiers in the remaster are all out of wack. + public static readonly TeamRemap RemapTdBlue = new TeamRemap("MULTI2", 2, 135, 176, new byte[] {2, 119, 118, 135, 136, 138, 112, 12, 118, 135, 136, 137, 138, 139, 114, 112}); + public static readonly TeamRemap RemapTdOrange = new TeamRemap("MULTI5", 24, 26, 176, new byte[] { 24, 25, 26, 27, 29, 31, 46, 47, 26, 27, 28, 29, 30, 31, 43, 47 }); + public static readonly TeamRemap RemapTdGreen = new TeamRemap("MULTI4", 167, 159, 176, new byte[] { 5, 165, 166, 167, 159, 142, 140, 199, 166, 167, 157, 3, 159, 143, 142, 141 }); + public static readonly TeamRemap RemapTdLtBlue = new TeamRemap("MULTI6", 201, 203, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 }); + public static readonly TeamRemap RemapTdYellow = new TeamRemap("MULTI1", 5, 157, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 }); + public static readonly TeamRemap RemapTdRed = new TeamRemap("MULTI3", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 }); + // Added extra colours for flags + public static readonly TeamRemap RemapTdMul7 = new TeamRemap("MULTI7", 146, 209, 176, new byte[] { 146, 152, 209, 151, 173, 150, 173, 183, 146, 152, 209, 151, 173, 150, 173, 183 }); // Brown + public static readonly TeamRemap RemapTdMul8 = new TeamRemap("MULTI8", 214, 213, 176, new byte[] { 132, 133, 134, 213, 214, 121, 120, 12, 133, 134, 213, 214, 121, 174, 120, 199 }); // Burgundy + public static readonly TeamRemap RemapTdNone = new TeamRemap("NONE", 199, 199, 176, new byte[] { 14, 195, 196, 13, 169, 198, 199, 112, 14, 195, 196, 13, 169, 198, 199, 112 }); // Black + + + private static readonly Dictionary RemapsTd; + + public const Byte MaxValueSixBit = 63; + private static readonly Byte[] ConvertToEightBit = new Byte[64]; + + static TeamRemapManager() + { + RemapsTd = (from field in typeof(ITeamColorManager).GetFields(BindingFlags.Static | BindingFlags.Public) + where field.IsInitOnly && typeof(TeamRemap).IsAssignableFrom(field.FieldType) && field.Name.StartsWith("RemapTd") + select field.GetValue(null) as TeamRemap).ToDictionary(trm => trm.Name); + + // Build easy lookup tables for this, so no calculations are ever needed for this later. + for (Int32 i = 0; i < 64; ++i) + ConvertToEightBit[i] = (Byte)Math.Round(i * 255.0 / 63.0, MidpointRounding.ToEven); + } + + private Dictionary remapsRa = new Dictionary(); + private GameType currentlyLoadedGameType; + private Color[] currentlyLoadedPalette; + private readonly IArchiveManager mixfileManager; + + + public ITeamColor this[string key] => GetforCurrentGame(key); + public Color GetBaseColor(string key) + { + TeamRemap tc = GetforCurrentGame(key); + if (tc != null) + { + return currentlyLoadedPalette[tc.UnitRadarColor]; + } + return RemapBaseColor; + } + + private TeamRemap GetforCurrentGame(string key) + { + if (string.IsNullOrEmpty(key)) + { + return null; + } + Dictionary currentRemaps; + switch (currentlyLoadedGameType) + { + case GameType.TiberianDawn: + case GameType.SoleSurvivor: + currentRemaps = RemapsTd; + break; + case GameType.RedAlert: + currentRemaps = this.remapsRa; + break; + default: + return null; + } + return currentRemaps.ContainsKey(key) ? currentRemaps[key] : null; + } + + private Color remapBaseColor = Color.Black; + public Color RemapBaseColor => remapBaseColor; + + public void Load(string path) + { + // Check if CPS, if so, fill in "this.remapsRa" + } + + public TeamRemapManager(IArchiveManager fileManager) + { + this.mixfileManager = fileManager; + } + + public void Reset(GameType gameType, string theater) + { + currentlyLoadedGameType = gameType; + currentlyLoadedPalette = new Color[0x100]; + // file manager should already be reset to read from the correct game at this point. + using (Stream palette = mixfileManager.OpenFile(theater + ".pal")) + { + if (palette != null) + { + byte[] pal; + using (BinaryReader sr = new BinaryReader(palette)) + { + pal = GeneralUtils.ReadAllBytes(sr); + } + int len = Math.Min(pal.Length / 3, 0x100); + int offs = 0; + for (int i = 0; i < len; ++i) + { + byte r = ConvertToEightBit[pal[offs + 0] & 0x3F]; + byte g = ConvertToEightBit[pal[offs + 1] & 0x3F]; + byte b = ConvertToEightBit[pal[offs + 2] & 0x3F]; + currentlyLoadedPalette[i] = Color.FromArgb(r, g, b); + offs += 3; + } + } + } + if (gameType == GameType.TiberianDawn || gameType == GameType.SoleSurvivor) + { + + } + } + + public void setTheater() + { + + } + } +} diff --git a/CnCTDRAMapEditor/Utility/TextureManager.cs b/CnCTDRAMapEditor/Utility/TextureManager.cs index 64c1b66..03b1979 100644 --- a/CnCTDRAMapEditor/Utility/TextureManager.cs +++ b/CnCTDRAMapEditor/Utility/TextureManager.cs @@ -33,25 +33,16 @@ namespace MobiusEditor.Utility private static string MissingTexture = "DATA\\ART\\TEXTURES\\SRGB\\COMMON\\MISC\\MISSING.TGA"; private bool processedMissingTexture = false; -#if false - private class ImageData - { - public TGA TGA; - public JObject Metadata; - } -#endif - private readonly IArchiveManager megafileManager; private Dictionary cachedTextures = new Dictionary(); - private Dictionary<(string, ITeamColor), (Bitmap, Rectangle)> teamColorTextures = new Dictionary<(string, ITeamColor), (Bitmap, Rectangle)>(); public TextureManager(IArchiveManager megafileManager) { this.megafileManager = megafileManager; } - public void Reset() + public void Reset(GameType gameType, string theater) { Bitmap[] cachedImages = cachedTextures.Values.ToArray(); cachedTextures.Clear(); @@ -67,20 +58,6 @@ namespace MobiusEditor.Utility // Ignore. } } - (Bitmap, Rectangle)[] cachedTeamImages = teamColorTextures.Values.ToArray(); - teamColorTextures.Clear(); - for (int i = 0; i < cachedTeamImages.Length; ++i) - { - try - { - (Bitmap bitmap, Rectangle opaqueBounds) = cachedTeamImages[i]; - bitmap.Dispose(); - } - catch - { - // Ignore. - } - } } public (Bitmap, Rectangle) GetTexture(string filename, ITeamColor teamColor, bool generateFallback) @@ -101,13 +78,9 @@ namespace MobiusEditor.Utility return (retCopy, bounds); } } - if (teamColorTextures.TryGetValue((filename, teamColor), out (Bitmap bitmap, Rectangle opaqueBounds) result)) - { - Bitmap retCopy = new Bitmap(result.bitmap); - retCopy.SetResolution(96, 96); - return (retCopy, result.opaqueBounds); - } - if (!cachedTextures.TryGetValue(filename, out result.bitmap)) + Bitmap resBitmap; + Rectangle resBounds = Rectangle.Empty; + if (!cachedTextures.TryGetValue(filename, out _)) { if (Path.GetExtension(filename).ToLower() == ".tga") { @@ -181,71 +154,8 @@ namespace MobiusEditor.Utility cachedTextures[filename] = bitmap; } } -#if false - // Attempt to load parent directory as archive - var archiveDir = Path.GetDirectoryName(filename); - var archivePath = archiveDir + ".ZIP"; - using (var fileStream = megafileManager.Open(archivePath)) - { - if (fileStream != null) - { - using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Read)) - { - var images = new Dictionary(); - foreach (var entry in archive.Entries) - { - var name = Path.GetFileNameWithoutExtension(entry.Name); - if (!images.TryGetValue(name, out ImageData imageData)) - { - imageData = images[name] = new ImageData { TGA = null, Metadata = null }; - } - if ((imageData.TGA == null) && (Path.GetExtension(entry.Name).ToLower() == ".tga")) - { - using (var stream = entry.Open()) - using (var memStream = new MemoryStream()) - { - stream.CopyTo(memStream); - imageData.TGA = new TGA(memStream); - } - } - else if ((imageData.Metadata == null) && (Path.GetExtension(entry.Name).ToLower() == ".meta")) - { - using (var stream = entry.Open()) - using (var reader = new StreamReader(stream)) - { - imageData.Metadata = JObject.Parse(reader.ReadToEnd()); - } - } - if ((imageData.TGA != null) && (imageData.Metadata != null)) - { - var bitmap = imageData.TGA.ToBitmap(true); - var size = new Size(imageData.Metadata["size"][0].ToObject(), imageData.Metadata["size"][1].ToObject()); - var crop = Rectangle.FromLTRB( - imageData.Metadata["crop"][0].ToObject(), - imageData.Metadata["crop"][1].ToObject(), - imageData.Metadata["crop"][2].ToObject(), - imageData.Metadata["crop"][3].ToObject() - ); - var uncroppedBitmap = new Bitmap(size.Width, size.Height, bitmap.PixelFormat); - uncroppedBitmap.SetResolution(96, 96); - using (var g = Graphics.FromImage(uncroppedBitmap)) - { - g.DrawImage(bitmap, crop, new Rectangle(Point.Empty, bitmap.Size), GraphicsUnit.Pixel); - } - cachedTextures[Path.Combine(archiveDir, name) + ".tga"] = uncroppedBitmap; - images.Remove(name); - } - } - foreach (var item in images.Where(x => x.Value.TGA != null)) - { - cachedTextures[Path.Combine(archiveDir, item.Key) + ".tga"] = item.Value.TGA.ToBitmap(true); - } - } - } - } -#endif } - if (!cachedTextures.TryGetValue(filename, out result.bitmap)) + if (!cachedTextures.TryGetValue(filename, out resBitmap)) { // Try loading as a DDS var ddsFilename = Path.ChangeExtension(filename, ".DDS"); @@ -260,26 +170,25 @@ namespace MobiusEditor.Utility } } } - if (!cachedTextures.TryGetValue(filename, out result.bitmap)) + if (!cachedTextures.TryGetValue(filename, out resBitmap)) { - return result; + return (resBitmap, resBounds); } - Bitmap resBm = new Bitmap(result.bitmap); + // Clone returned image. + Bitmap resBm = new Bitmap(resBitmap); resBm.SetResolution(96, 96); - result.bitmap = resBm; + resBitmap = resBm; if (teamColor != null) { Rectangle opaqueBounds; teamColor.ApplyToImage(resBm, out opaqueBounds); - result.opaqueBounds = opaqueBounds; - // EXPERIMENTAL: might be better not to cache this? - //teamColorTextures[(filename, teamColor)] = (new Bitmap(result.bitmap), result.opaqueBounds); + resBounds = opaqueBounds; } else { - result.opaqueBounds = ImageUtils.CalculateOpaqueBounds(resBm); + resBounds = ImageUtils.CalculateOpaqueBounds(resBm); } - return result; + return (resBitmap, resBounds); } private void LoadTgaFromZipFileStream(Stream fileStream, String name, ref TGA tga, ref JObject metadata) diff --git a/CnCTDRAMapEditor/Utility/Tileset.cs b/CnCTDRAMapEditor/Utility/Tileset.cs index 7acba72..430e104 100644 --- a/CnCTDRAMapEditor/Utility/Tileset.cs +++ b/CnCTDRAMapEditor/Utility/Tileset.cs @@ -23,7 +23,7 @@ using System.Xml; namespace MobiusEditor.Utility { - public class Tile + public class Tile: IDisposable { public Bitmap Image { get; private set; } @@ -39,6 +39,24 @@ namespace MobiusEditor.Utility : this(image, new Rectangle(0, 0, image.Width, image.Height)) { } + + public void Dispose() + { + Bitmap image = this.Image; + this.Image = null; + try + { + if (image != null) + { + image.Dispose(); + } + } + catch + { + // ignore. + } + + } } public class Tileset @@ -77,6 +95,17 @@ namespace MobiusEditor.Utility { foreach (var tileItem in item.Value) { + // no need to dispose the images; the Reset of TextureManager handles that. + foreach (KeyValuePair tileinfo in tileItem.Value.TeamColorTiles) + { + if (tileinfo.Value != null) { + foreach (Tile tile in tileinfo.Value) + { + // clean up bitmap + tile.Dispose(); + } + } + } tileItem.Value.TeamColorTiles.Clear(); } }