* Progress on classic color management

* Added cleanup routines for tilesets and object type previews.
This commit is contained in:
Nyerguds 2023-05-31 23:51:28 +02:00
parent 02d1992055
commit 303762eb31
22 changed files with 450 additions and 192 deletions

View File

@ -618,6 +618,8 @@
<Compile Include="Utility\SteamworksUGC.cs" />
<Compile Include="Utility\TeamColor.cs" />
<Compile Include="Utility\TeamColorManager.cs" />
<Compile Include="Utility\TeamRemap.cs" />
<Compile Include="Utility\TeamRemapManager.cs" />
<Compile Include="Utility\TextureManager.cs" />
<Compile Include="Utility\TGASharpLib.cs" />
<Compile Include="Utility\Tileset.cs" />

View File

@ -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();
}
}

View File

@ -14,8 +14,6 @@ namespace MobiusEditor.Interface
{
/// <summary>Name of the color, used as identifier.</summary>
string Name { get; }
/// <summary>A general color representing this team color.</summary>
Color BaseColor { get; }
/// <summary>
/// Apply this color to a given image.

View File

@ -15,7 +15,12 @@ namespace MobiusEditor.Interface
Color RemapBaseColor { get; }
ITeamColor this[string key] { get; }
/// <summary>Gets a general color representing this team color.</summary>
/// <param name="key">Color key</param>
/// <returns>The basic color for this team color.</returns>
Color GetBaseColor(string key);
void Load(string path);
void Reset(GameType gameType);
void Reset(GameType gameType, string theater);
}
}

View File

@ -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);
}
}
/// <summary>
/// The separate-threaded part for making a new map.
/// </summary>
@ -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
/// <param name="isTdMegaMap"></param>
/// <param name="modPaths"></param>
/// <returns></returns>
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;

View File

@ -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;
/// <summary>
/// Indicates buildings that have pieces sticking out at the top that should not overlap the objects on these cells.
/// </summary>
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<Graphics>, 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 */ }
}
}
}
}

View File

@ -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 */ }
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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 */ }
}
}
}
}

View File

@ -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 */ }
}
}
}
}

View File

@ -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 */ }
}
}
}
}

View File

@ -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 */ }
}
}
}
}

View File

@ -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 */ }
}
}
}
}

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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<byte>().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<byte>().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();
}
}
}

View File

@ -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<string, TeamRemap> 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<string, TeamRemap> remapsRa = new Dictionary<string, TeamRemap>();
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<string, TeamRemap> 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()
{
}
}
}

View File

@ -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<string, Bitmap> cachedTextures = new Dictionary<string, Bitmap>();
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<string, ImageData>();
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<int>(), imageData.Metadata["size"][1].ToObject<int>());
var crop = Rectangle.FromLTRB(
imageData.Metadata["crop"][0].ToObject<int>(),
imageData.Metadata["crop"][1].ToObject<int>(),
imageData.Metadata["crop"][2].ToObject<int>(),
imageData.Metadata["crop"][3].ToObject<int>()
);
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)

View File

@ -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<string, Tile[]> tileinfo in tileItem.Value.TeamColorTiles)
{
if (tileinfo.Value != null) {
foreach (Tile tile in tileinfo.Value)
{
// clean up bitmap
tile.Dispose();
}
}
}
tileItem.Value.TeamColorTiles.Clear();
}
}