More work towards classic integration.

* Centralised theater usage to the Reset functions, so it no longer needs to be given to all drawing calls.
* Added interface for TilesetManager
* Added MixfileManager and Mixfile classes
* Removed TextureManager from globals; it is now a purely internal thing in TilesetManager.
This commit is contained in:
Nyerguds 2023-06-14 20:46:54 +02:00
parent 996de77eb7
commit 8378c69c05
34 changed files with 987 additions and 235 deletions

View File

@ -330,6 +330,7 @@
<Compile Include="Interface\ITeamColor.cs" />
<Compile Include="Interface\ITechno.cs" />
<Compile Include="Interface\ITechnoType.cs" />
<Compile Include="Interface\ITilesetManager.cs" />
<Compile Include="Interface\ITool.cs" />
<Compile Include="Interface\IWidget.cs" />
<Compile Include="Interface\ToolType.cs" />
@ -598,12 +599,13 @@
<Compile Include="Tools\WaypointsTool.cs" />
<Compile Include="Utility\BooleanStringStyle.cs" />
<Compile Include="Utility\ClassicSpriteLoader.cs" />
<Compile Include="Utility\CRC.cs" />
<Compile Include="Utility\ExtensionMethods.cs" />
<Compile Include="Utility\GameTextManager.cs" />
<Compile Include="Utility\GameTextManagerClassic.cs" />
<Compile Include="Utility\GeneralUtils.cs" />
<Compile Include="Utility\GenericBooleanTypeConverter.cs" />
<Compile Include="Utility\Hashing\CRC.cs" />
<Compile Include="Utility\Hashing\Rol.cs" />
<Compile Include="Utility\ImageUtils.cs" />
<Compile Include="Utility\INI.cs" />
<Compile Include="Utility\INITools.cs" />
@ -612,6 +614,8 @@
<Compile Include="Utility\Megafile.cs" />
<Compile Include="Utility\MegafileBuilder.cs" />
<Compile Include="Utility\MegafileManager.cs" />
<Compile Include="Utility\Mixfile.cs" />
<Compile Include="Utility\MixfileManager.cs" />
<Compile Include="Utility\MRU.cs" />
<Compile Include="Utility\PropertyTracker.cs" />
<Compile Include="Event\MapRefreshEventArgs.cs" />

View File

@ -33,6 +33,8 @@
this.btnBrowse = new System.Windows.Forms.Button();
this.btnContinue = new System.Windows.Forms.Button();
this.btnQuit = new System.Windows.Forms.Button();
this.btnClassic = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
@ -46,14 +48,17 @@
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(12, 39);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(301, 20);
this.textBox1.Size = new System.Drawing.Size(310, 20);
this.textBox1.TabIndex = 1;
//
// btnBrowse
//
this.btnBrowse.Location = new System.Drawing.Point(319, 37);
this.btnBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnBrowse.Location = new System.Drawing.Point(328, 37);
this.btnBrowse.Name = "btnBrowse";
this.btnBrowse.Size = new System.Drawing.Size(84, 23);
this.btnBrowse.TabIndex = 2;
@ -63,40 +68,71 @@
//
// btnContinue
//
this.btnContinue.Location = new System.Drawing.Point(319, 97);
this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnContinue.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnContinue.Location = new System.Drawing.Point(328, 106);
this.btnContinue.Name = "btnContinue";
this.btnContinue.Size = new System.Drawing.Size(84, 23);
this.btnContinue.TabIndex = 3;
this.btnContinue.TabIndex = 5;
this.btnContinue.Text = "OK";
this.btnContinue.UseVisualStyleBackColor = true;
this.btnContinue.Click += new System.EventHandler(this.btnContinue_Click);
//
// btnQuit
//
this.btnQuit.Location = new System.Drawing.Point(229, 97);
this.btnQuit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnQuit.DialogResult = System.Windows.Forms.DialogResult.No;
this.btnQuit.Location = new System.Drawing.Point(12, 106);
this.btnQuit.Name = "btnQuit";
this.btnQuit.Size = new System.Drawing.Size(84, 23);
this.btnQuit.TabIndex = 4;
this.btnQuit.Text = "Quit Editor";
this.btnQuit.UseVisualStyleBackColor = true;
this.btnQuit.Visible = false;
this.btnQuit.Click += new System.EventHandler(this.btnQuit_Click);
//
// btnClassic
//
this.btnClassic.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnClassic.Location = new System.Drawing.Point(161, 106);
this.btnClassic.Name = "btnClassic";
this.btnClassic.Size = new System.Drawing.Size(161, 23);
this.btnClassic.TabIndex = 3;
this.btnClassic.Text = "Continue with classic graphics";
this.btnClassic.UseVisualStyleBackColor = true;
this.btnClassic.Click += new System.EventHandler(this.btnClassic_Click);
//
// label2
//
this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.label2.Location = new System.Drawing.Point(13, 70);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(395, 33);
this.label2.TabIndex = 0;
this.label2.Text = "To skip this dialog and always start with the classic graphics, edit CnCTDRAMapEd" +
"itor.exe.config and set the <ClassicGraphics> setting to True.";
//
// GameInstallationPathForm
//
this.AcceptButton = this.btnContinue;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(419, 132);
this.CancelButton = this.btnClassic;
this.ClientSize = new System.Drawing.Size(424, 141);
this.ControlBox = false;
this.Controls.Add(this.btnClassic);
this.Controls.Add(this.btnQuit);
this.Controls.Add(this.btnContinue);
this.Controls.Add(this.btnBrowse);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "GameInstallationPathForm";
this.Text = "Select game installation path";
this.Text = "Select C&C Remastered installation path";
this.ResumeLayout(false);
this.PerformLayout();
@ -109,5 +145,7 @@
private System.Windows.Forms.Button btnBrowse;
private System.Windows.Forms.Button btnContinue;
private System.Windows.Forms.Button btnQuit;
private System.Windows.Forms.Button btnClassic;
private System.Windows.Forms.Label label2;
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Drawing;
using System.IO;
using System.Security;
using System.Windows.Forms;
@ -55,7 +56,7 @@ namespace MobiusEditor
return;
}
textBox1.Text = dir;
DialogResult = DialogResult.OK;
DialogResult = DialogResult.Yes;
Close();
}
@ -64,5 +65,11 @@ namespace MobiusEditor
DialogResult = DialogResult.No;
Close();
}
private void btnClassic_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@ -24,6 +24,7 @@ namespace MobiusEditor
{
static Globals()
{
SetTileSize(false);
double minScale = 1.0 / Math.Min(OriginalTileWidth, OriginalTileHeight);
// Startup options
UseClassicGraphics = Properties.Settings.Default.UseClassicGraphics;
@ -68,9 +69,15 @@ namespace MobiusEditor
public const string MegafilePath = @"DATA";
public const string GameTextFilenameFormat = @"DATA\TEXT\MASTERTEXTFILE_{0}.LOC";
public const int OriginalTileWidth = 128;
public const int OriginalTileHeight = 128;
public static readonly Size OriginalTileSize = new Size(OriginalTileWidth, OriginalTileHeight);
public static void SetTileSize(bool classic)
{
OriginalTileWidth = classic ? 24 : 128;
OriginalTileHeight = classic ? 24 : 128;
}
public static int OriginalTileWidth { get; private set; }
public static int OriginalTileHeight { get; private set; }
public static Size OriginalTileSize => new Size(OriginalTileWidth, OriginalTileHeight);
public const int PixelWidth = 24;
public const int PixelHeight = 24;
@ -134,8 +141,7 @@ namespace MobiusEditor
public const long MaxMapSize = 0x20000;
public static IArchiveManager TheArchiveManager;
public static TextureManager TheTextureManager;
public static TilesetManager TheTilesetManager;
public static ITilesetManager TheTilesetManager;
public static ITeamColorManager TheTeamColorManager;
public static IGameTextManager TheGameTextManager;

View File

@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using MobiusEditor.Model;
namespace MobiusEditor.Interface
{
@ -9,10 +10,9 @@ namespace MobiusEditor.Interface
{
String LoadRoot { get; }
bool LoadArchive(GameType gameType, string archivePath);
bool FileExists(string path);
Stream OpenFile(string path);
void Reset(GameType gameType);
void Reset(GameType gameType, TheaterType theater);
string[] ExpandModPaths { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using MobiusEditor.Model;
using MobiusEditor.Utility;
namespace MobiusEditor.Interface
{
public interface ITilesetManager
{
void Reset(TheaterType theater);
bool GetTeamColorTileData(string name, int shape, ITeamColor teamColor, out Tile tile, bool generateFallback, bool onlyIfDefined);
bool GetTeamColorTileData(string name, int shape, ITeamColor teamColor, out Tile tile);
bool GetTileData(string name, int shape, out Tile tile, bool generateFallback, bool onlyIfDefined);
bool GetTileData(string name, int shape, out Tile tile);
int GetTileDataLength(string name);
}
}

View File

@ -1335,10 +1335,10 @@ namespace MobiusEditor
TheaterType theaterType = ttc.ConvertFrom(new MapContext(plugin.Map, false), theater);
// Resetting to a specific game type will take care of classic mode.
Globals.TheArchiveManager.ExpandModPaths = modPaths;
Globals.TheArchiveManager.Reset(gameType);
Globals.TheArchiveManager.Reset(gameType, theaterType);
Globals.TheGameTextManager.Reset(gameType);
Globals.TheTextureManager.Reset(gameType, theaterType);
Globals.TheTilesetManager.Reset();
//Globals.TheTextureManager.Reset(gameType, theaterType);
Globals.TheTilesetManager.Reset(theaterType);
Globals.TheTeamColorManager.Reset(gameType, theaterType);
// Load game-specific data
if (gameType == GameType.TiberianDawn)
@ -1665,8 +1665,7 @@ namespace MobiusEditor
pl.Dispose();
}
// Unload graphics
Globals.TheTilesetManager.Reset();
Globals.TheTextureManager.Reset(GameType.None, null);
Globals.TheTilesetManager.Reset(null);
// Clean up loaded file status
filename = null;
loadedFileType = FileType.None;
@ -2398,7 +2397,7 @@ namespace MobiusEditor
Tile templateTile = null;
if (template != null)
{
Globals.TheTilesetManager.GetTileData(plugin.Map.Theater.Tilesets, template.Name, template.GetIconIndex(template.GetFirstValidIcon()), out templateTile, false, true);
Globals.TheTilesetManager.GetTileData(template.Name, template.GetIconIndex(template.GetFirstValidIcon()), out templateTile);
}
// For the following, check if the thumbnail was initialised.
SmudgeType smudge = plugin.Map.SmudgeTypes.Where(sm => !sm.IsAutoBib && sm.Icons == 1 && sm.Size.Width == 1 && sm.Size.Height == 1 && sm.Thumbnail != null
@ -2424,13 +2423,13 @@ namespace MobiusEditor
&& (!Globals.FilterTheaterObjects || ov.Theaters == null || ov.Theaters.Contains(plugin.Map.Theater))).OrderBy(ov => ov.ID).FirstOrDefault();
OverlayType wall = plugin.Map.OverlayTypes.Where(ov => (ov.Flag & OverlayTypeFlag.Wall) == OverlayTypeFlag.Wall
&& (!Globals.FilterTheaterObjects || ov.Theaters == null || ov.Theaters.Contains(plugin.Map.Theater))).OrderBy(ov => ov.ID).FirstOrDefault();
bool gotBeacon = Globals.TheTilesetManager.GetTileData(plugin.Map.Theater.Tilesets, "beacon", 0, out Tile waypoint, false, true);
bool gotBeacon = Globals.TheTilesetManager.GetTileData("beacon", 0, out Tile waypoint);
if (!gotBeacon)
{
// Beacon only exists in rematered graphics. Get fallback.
Globals.TheTilesetManager.GetTileData(plugin.Map.Theater.Tilesets, "armor", 6, out waypoint, false, true);
Globals.TheTilesetManager.GetTileData("armor", 6, out waypoint);
}
Globals.TheTilesetManager.GetTileData(plugin.Map.Theater.Tilesets, "mine", 3, out Tile cellTrigger, false, true);
Globals.TheTilesetManager.GetTileData("mine", 3, out Tile cellTrigger);
LoadNewIcon(mapToolStripButton, templateTile?.Image, plugin, 0);
LoadNewIcon(smudgeToolStripButton, smudge?.Thumbnail, plugin, 1);
//LoadNewIcon(overlayToolStripButton, overlayTile?.Image, plugin, 2);
@ -2446,9 +2445,13 @@ namespace MobiusEditor
// The Texture manager returns a clone of its own cached image. The Tileset manager caches those clones,
// and is responsible for their cleanup, but if we use it directly it needs to be disposed.
// Icon: chrono cursor from TEXTURES_SRGB.MEG
using (Bitmap select = Globals.TheTextureManager.GetTexture(@"DATA\ART\TEXTURES\SRGB\ICON_SELECT_GREEN_04.DDS", null, false).Item1)
if (Globals.TheTilesetManager is TilesetManager tsm)
{
LoadNewIcon(selectToolStripButton, select, plugin, 11, false);
// Loaded without tileset manager, in modern only. Will need to fix this for classic later.
using (Bitmap select = tsm.TextureManager.GetTexture(@"DATA\ART\TEXTURES\SRGB\ICON_SELECT_GREEN_04.DDS", null, false).Item1)
{
LoadNewIcon(selectToolStripButton, select, plugin, 11, false);
}
}
}
private void LoadNewIcon(ViewToolStripButton button, Bitmap image, IGamePlugin plugin, int index)

View File

@ -258,7 +258,7 @@ namespace MobiusEditor.Model
return (this.Name ?? String.Empty).ToUpperInvariant();
}
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
public void Init(GameType gameType, HouseType house, DirectionType direction)
{
Bitmap oldImage = this.Thumbnail;
Building mockBuilding = new Building()
@ -268,7 +268,7 @@ namespace MobiusEditor.Model
Strength = 256,
Direction = direction
};
(Rectangle, Action<Graphics>, bool) render = MapRenderer.RenderBuilding(gameType, theater, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding);
(Rectangle, Action<Graphics>, bool) render = MapRenderer.RenderBuilding(gameType, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding);
if (!render.Item1.IsEmpty)
{
Bitmap th = new Bitmap(render.Item1.Width, render.Item1.Height);

View File

@ -94,10 +94,10 @@ namespace MobiusEditor.Model
return Name;
}
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
public void Init(HouseType house, DirectionType direction)
{
var oldImage = Thumbnail;
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 4, out Tile tile))
if (Globals.TheTilesetManager.GetTileData(Name, 4, out Tile tile))
{
_RenderSize = tile.Image.Size;
}
@ -113,7 +113,7 @@ namespace MobiusEditor.Model
using (var g = Graphics.FromImage(infantryThumbnail))
{
MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale);
MapRenderer.RenderInfantry(theater, Point.Empty, Globals.PreviewTileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
MapRenderer.RenderInfantry(Point.Empty, Globals.PreviewTileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
}
Thumbnail = infantryThumbnail;
if (oldImage != null)

View File

@ -527,31 +527,31 @@ namespace MobiusEditor.Model
}
foreach (SmudgeType smudgeType in this.SmudgeTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
smudgeType.Init(this.Theater);
smudgeType.Init();
}
foreach (OverlayType overlayType in this.OverlayTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
overlayType.Init(gameType, this.Theater);
overlayType.Init(gameType);
}
foreach (TerrainType terrainType in this.TerrainTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
terrainType.Init(this.Theater);
terrainType.Init();
}
// Ignore expansion status for these; they can still be enabled later.
DirectionType infDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.South).First();
foreach (InfantryType infantryType in this.AllInfantryTypes)
{
infantryType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), infDir);
infantryType.Init(this.HouseTypesIncludingNone.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), infDir);
}
DirectionType unitDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.SouthWest).First();
foreach (UnitType unitType in this.AllUnitTypes)
{
unitType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), unitDir);
unitType.Init(gameType, this.HouseTypesIncludingNone.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), unitDir);
}
DirectionType bldDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.North).First();
foreach (BuildingType buildingType in this.BuildingTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
buildingType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(buildingType.OwnerHouse)).FirstOrDefault(), bldDir);
buildingType.Init(gameType, this.HouseTypesIncludingNone.Where(h => h.Equals(buildingType.OwnerHouse)).FirstOrDefault(), bldDir);
}
}

View File

@ -168,7 +168,7 @@ namespace MobiusEditor.Model
return Name;
}
public void Init(GameType gameType, TheaterType theater)
public void Init(GameType gameType)
{
var oldImage = Thumbnail;
var tileSize = Globals.PreviewTileSize;
@ -182,7 +182,7 @@ namespace MobiusEditor.Model
Icon = tilenr,
};
MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale);
MapRenderer.RenderOverlay(gameType, theater, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockOverlay).Item2(g);
MapRenderer.RenderOverlay(gameType, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockOverlay).Item2(g);
}
Thumbnail = th;
if (oldImage != null)

View File

@ -140,7 +140,7 @@ namespace MobiusEditor.Model
return Name;
}
public void Init(TheaterType theater)
public void Init()
{
var oldImage = Thumbnail;
var tileSize = Globals.PreviewTileSize;
@ -155,7 +155,7 @@ namespace MobiusEditor.Model
{
for (int x = 0; x < Size.Width; x++)
{
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, icon++, out Tile tile))
if (Globals.TheTilesetManager.GetTileData(Name, icon++, out Tile tile))
{
found = true;
Rectangle overlayBounds = MapRenderer.RenderBounds(tile.Image.Size, new Size(1, 1), Globals.PreviewTileScale);

View File

@ -330,7 +330,7 @@ namespace MobiusEditor.Model
// This allows mods to add 'random' tiles to existing 1x1 tiles. Check excludes 'Clear' terrain and items already defined as random.
if (IconWidth == 1 & IconHeight == 1 && (Flag & TemplateTypeFlag.Clear) == TemplateTypeFlag.None && (Flag & TemplateTypeFlag.RandomCell) == TemplateTypeFlag.None)
{
if (Globals.TheTilesetManager.GetTileDataLength(theater.Tilesets, Name) > 1)
if (Globals.TheTilesetManager.GetTileDataLength(Name) > 1)
{
Flag |= TemplateTypeFlag.RandomCell;
}
@ -351,7 +351,7 @@ namespace MobiusEditor.Model
}
else
{
numIcons = Globals.TheTilesetManager.GetTileDataLength(theater.Tilesets, Name);
numIcons = Globals.TheTilesetManager.GetTileDataLength(Name);
}
numIcons = Math.Max(1, numIcons);
NumIcons = numIcons;
@ -404,7 +404,7 @@ namespace MobiusEditor.Model
tryDummy = forceDummy;
}
// Fetch dummy if definitely in bounds, first cell of a random one, or dummy is forced.
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, nameToFetch, iconToFetch, out Tile tile, tryDummy, forceDummy || !isRandom || (x == 0 && y == 0)))
if (Globals.TheTilesetManager.GetTileData(nameToFetch, iconToFetch, out Tile tile, tryDummy, forceDummy || !isRandom || (x == 0 && y == 0)))
{
if (tile.Image != null)
{

View File

@ -190,11 +190,11 @@ namespace MobiusEditor.Model
return (Name ?? String.Empty).ToUpperInvariant();
}
public void Init(TheaterType theater)
public void Init()
{
var oldImage = Thumbnail;
string tileName = GraphicsSource;
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, tileName, DisplayIcon, out Tile tile))
if (Globals.TheTilesetManager.GetTileData(tileName, DisplayIcon, out Tile tile))
{
var tileSize = Globals.PreviewTileSize;
var renderSize = new Size(tileSize.Width * Size.Width, tileSize.Height * Size.Height);

View File

@ -146,10 +146,10 @@ namespace MobiusEditor.Model
return Name;
}
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
public void Init(GameType gameType, HouseType house, DirectionType direction)
{
var oldImage = Thumbnail;
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 0, out Tile tile))
if (Globals.TheTilesetManager.GetTileData(Name, 0, out Tile tile))
{
_RenderSize = tile.Image.Size;
}
@ -170,7 +170,7 @@ namespace MobiusEditor.Model
using (var g = Graphics.FromImage(bigThumbnail))
{
MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale);
MapRenderer.RenderUnit(gameType, theater, new Point(1, 1), Globals.PreviewTileSize, mockUnit).Item2(g);
MapRenderer.RenderUnit(gameType, new Point(1, 1), Globals.PreviewTileSize, mockUnit).Item2(g);
}
using (var g2 = Graphics.FromImage(unitThumbnail))
{

View File

@ -77,7 +77,14 @@ namespace MobiusEditor
else
{
#if CLASSICIMPLEMENTED
LoadEditorClassic();
if (Globals.UseClassicGraphics)
{
LoadEditorClassic();
}
else
{
return;
}
#else
return;
#endif
@ -122,6 +129,7 @@ namespace MobiusEditor
private static void LoadEditorClassic()
{
Globals.SetTileSize(true);
#if CLASSICIMPLEMENTED
// The system should scan all mix archives for known filenames of other mix archives so it can do recursive searches.
// Mix files should be given in order or depth, so first give ones that are in the folder, then ones that may occur inside others.
@ -130,33 +138,46 @@ namespace MobiusEditor
gameFolders.Add(GameType.TiberianDawn, "Classic\\TD\\");
gameFolders.Add(GameType.RedAlert, "Classic\\RA\\");
gameFolders.Add(GameType.SoleSurvivor, "Classic\\TD\\");
//Globals.TheArchiveManager = new MixfileManager(ApplicationPath, gameFolders);
MixfileManager mfm = new MixfileManager(ApplicationPath, gameFolders);
Globals.TheArchiveManager = mfm;
var mixfilesLoaded = true;
// This will map the mix files to the respective games, and look for them in the respective folders.
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.TiberianDawn, "cclocal.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.TiberianDawn, "conquer.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.TiberianDawn, "desert.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.TiberianDawn, "temperat.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.TiberianDawn, "winter.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.SoleSurvivor, "cclocal.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.SoleSurvivor, "conquer.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.SoleSurvivor, "desert.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.SoleSurvivor, "temperat.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.SoleSurvivor, "winter.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "expand2.mix");
// All graphics from expand are also in expand2
//mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "expand.mix");
Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "redalert.mix");
Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "main.mix");
// Only needed for conquer.eng, and expand* override those anyway.
//mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "local.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "conquer.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "lores.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "lores1.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "temperat.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "snow.mix");
mixfilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.RedAlert, "interior.mix");
// Tiberian Dawn
mixfilesLoaded &= mfm.LoadArchive(GameType.TiberianDawn, "cclocal.mix", false, false);
mixfilesLoaded &= mfm.LoadArchive(GameType.TiberianDawn, "conquer.mix", false, false);
// Tiberian Dawn Theaters
mixfilesLoaded &= mfm.LoadArchive(GameType.TiberianDawn, "desert.mix", false, false, true);
mixfilesLoaded &= mfm.LoadArchive(GameType.TiberianDawn, "temperat.mix", false, false, true);
mixfilesLoaded &= mfm.LoadArchive(GameType.TiberianDawn, "winter.mix", false, false, true);
// Sole Survivor
mixfilesLoaded &= mfm.LoadArchive(GameType.SoleSurvivor, "cclocal.mix", false, false);
mixfilesLoaded &= mfm.LoadArchive(GameType.SoleSurvivor, "conquer.mix", false, false);
// Sole Survivor Theaters
mixfilesLoaded &= mfm.LoadArchive(GameType.SoleSurvivor, "desert.mix", false, false, true);
mixfilesLoaded &= mfm.LoadArchive(GameType.SoleSurvivor, "temperat.mix", false, false, true);
mixfilesLoaded &= mfm.LoadArchive(GameType.SoleSurvivor, "winter.mix", false, false, true);
// Red Alert
// Aftermath expand file. Required.
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "expand2.mix", false, false);
// Counterstrike expand file. All graphics from expand are also in expand2.mix,
// but it could be used in modding to override different files. Not considered vital.
mfm.LoadArchive(GameType.RedAlert, "expand.mix", false, false);
// Container archives, so not considered vital.
mfm.LoadArchive(GameType.RedAlert, "redalert.mix", true, false);
mfm.LoadArchive(GameType.RedAlert, "main.mix", true, false);
// Needed for theater palettes and the remap settings in palette.cps
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "local.mix", false, true);
// Main graphics archive
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "conquer.mix", false, true);
// Infantry
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "lores.mix", false, true);
// Expansion infantry
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "lores1.mix", false, true);
// Theaters
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "temperat.mix", false, true, true);
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "snow.mix", false, true, true);
mixfilesLoaded &= mfm.LoadArchive(GameType.RedAlert, "interior.mix", false, true, true);
#if !DEVELOPER
if (!mixfilesLoaded)
{
@ -164,20 +185,31 @@ namespace MobiusEditor
return;
}
#endif
// Initialize texture, tileset, team color, and game text managers
// TODO
// TilesetManager: is the system graphics are requested from, possibly with house remap.
//Globals.TheTilesetManager = new ClassicTilesetManager(mfm);
// All the same. Would introduce region-based language differences, but the French and German files are... also called "conquer.eng".
Dictionary<GameType, String> gameStringsFiles = new Dictionary<GameType, string>();
gameStringsFiles.Add(GameType.TiberianDawn, "conquer.eng");
gameStringsFiles.Add(GameType.RedAlert, "conquer.eng");
gameStringsFiles.Add(GameType.SoleSurvivor, "conquer.eng");
Globals.TheTeamColorManager = new TeamRemapManager(mfm);
Globals.TheGameTextManager = new GameTextManagerClassic(mfm, gameStringsFiles);
#endif
}
private static void LoadEditorRemastered(String runPath)
{
// Initialize megafiles
Globals.TheArchiveManager = new MegafileManager(Path.Combine(runPath, Globals.MegafilePath), runPath);
MegafileManager mfm = new MegafileManager(Path.Combine(runPath, Globals.MegafilePath), runPath);
var megafilesLoaded = true;
megafilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.None, "CONFIG.MEG");
megafilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.None, "TEXTURES_COMMON_SRGB.MEG");
megafilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.None, "TEXTURES_RA_SRGB.MEG");
megafilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.None, "TEXTURES_SRGB.MEG");
megafilesLoaded &= Globals.TheArchiveManager.LoadArchive(GameType.None, "TEXTURES_TD_SRGB.MEG");
megafilesLoaded &= mfm.LoadArchive("CONFIG.MEG");
megafilesLoaded &= mfm.LoadArchive("TEXTURES_COMMON_SRGB.MEG");
megafilesLoaded &= mfm.LoadArchive("TEXTURES_RA_SRGB.MEG");
megafilesLoaded &= mfm.LoadArchive("TEXTURES_SRGB.MEG");
megafilesLoaded &= mfm.LoadArchive("TEXTURES_TD_SRGB.MEG");
#if !DEVELOPER
if (!megafilesLoaded)
{
@ -185,9 +217,10 @@ namespace MobiusEditor
return;
}
#endif
Globals.TheArchiveManager = mfm;
// Initialize texture, tileset, team color, and game text managers
Globals.TheTextureManager = new TextureManager(Globals.TheArchiveManager);
Globals.TheTilesetManager = new TilesetManager(Globals.TheArchiveManager, Globals.TheTextureManager, Globals.TilesetsXMLPath, Globals.TexturesPath);
TextureManager txm = new TextureManager(Globals.TheArchiveManager);
Globals.TheTilesetManager = new TilesetManager(Globals.TheArchiveManager, txm, Globals.TilesetsXMLPath, Globals.TexturesPath);
Globals.TheTeamColorManager = new TeamColorManager(Globals.TheArchiveManager);
// Not adapted to mods for now...
var cultureName = CultureInfo.CurrentUICulture.Name;
@ -212,12 +245,12 @@ namespace MobiusEditor
}
// If it does not exist, try to use the directory from the settings.
bool validSavedDirectory = false;
if (!String.IsNullOrWhiteSpace(Properties.Settings.Default.GameDirectoryPath) &&
Directory.Exists(Properties.Settings.Default.GameDirectoryPath))
string savedPath = (Properties.Settings.Default.GameDirectoryPath ?? String.Empty).Trim();
if (savedPath.Length > 0 && Directory.Exists(savedPath))
{
if (FileTest(Properties.Settings.Default.GameDirectoryPath))
if (FileTest(savedPath))
{
runPath = Properties.Settings.Default.GameDirectoryPath;
runPath = savedPath;
validSavedDirectory = true;
}
}
@ -240,11 +273,19 @@ namespace MobiusEditor
if (!validSavedDirectory)
{
var gameInstallationPathForm = new GameInstallationPathForm();
if (gameInstallationPathForm.ShowDialog() == DialogResult.No)
return null;
runPath = Path.GetDirectoryName(gameInstallationPathForm.SelectedPath);
Properties.Settings.Default.GameDirectoryPath = runPath;
Properties.Settings.Default.Save();
switch (gameInstallationPathForm.ShowDialog())
{
case DialogResult.OK:
runPath = Path.GetDirectoryName(gameInstallationPathForm.SelectedPath);
Properties.Settings.Default.GameDirectoryPath = runPath;
Properties.Settings.Default.Save();
break;
case DialogResult.No: // No longer used; cancelling will always fall back to classic graphics.
return null;
case DialogResult.Cancel:
Globals.UseClassicGraphics = true;
return null;
}
}
return runPath;
}

View File

@ -495,7 +495,7 @@ namespace MobiusEditor.RedAlert
throw new ApplicationException("Cannot find the necessary file inside the " + Path.GetFileName(path) + " archive.");
}
INI ini = new INI();
using (BinaryReader reader = new BinaryReader(megafile.Open(mprFile)))
using (BinaryReader reader = new BinaryReader(megafile.OpenFile(mprFile)))
{
iniBytes = reader.ReadAllBytes();
ParseIniContent(ini, iniBytes);

View File

@ -195,7 +195,7 @@ namespace MobiusEditor.Render
icon = 0;
}
// If it's actually placed on the map, show it, even if it has no graphics.
if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, name, icon, out Tile tile, true, false))
if (Globals.TheTilesetManager.GetTileData(name, icon, out Tile tile, true, false))
{
var renderBounds = new Rectangle(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height, tileSize.Width, tileSize.Height);
if(tile.Image != null)
@ -256,7 +256,7 @@ namespace MobiusEditor.Render
bool paintAsOverlay = overlay.Type.IsOverlay && (layers & MapLayerFlag.Overlay) != MapLayerFlag.None;
if (paintAsWall || paintAsResource || paintAsOverlay)
{
RenderOverlay(gameType, map.Theater, location, tileSize, tileScale, overlay).Item2(graphics);
RenderOverlay(gameType, location, tileSize, tileScale, overlay).Item2(graphics);
}
}
}
@ -280,7 +280,7 @@ namespace MobiusEditor.Render
{
continue;
}
overlappingRenderList.Add(RenderBuilding(gameType, map.Theater, topLeft, tileSize, tileScale, building));
overlappingRenderList.Add(RenderBuilding(gameType, topLeft, tileSize, tileScale, building));
}
}
if ((layers & MapLayerFlag.Infantry) != MapLayerFlag.None)
@ -298,7 +298,7 @@ namespace MobiusEditor.Render
{
continue;
}
overlappingRenderList.Add(RenderInfantry(map.Theater, topLeft, tileSize, infantry, (InfantryStoppingType)i));
overlappingRenderList.Add(RenderInfantry(topLeft, tileSize, infantry, (InfantryStoppingType)i));
}
}
}
@ -310,7 +310,7 @@ namespace MobiusEditor.Render
{
continue;
}
overlappingRenderList.Add(RenderUnit(gameType, map.Theater, topLeft, tileSize, unit));
overlappingRenderList.Add(RenderUnit(gameType, topLeft, tileSize, unit));
}
}
// Paint flat items (like the repair bay)
@ -333,7 +333,7 @@ namespace MobiusEditor.Render
{
continue;
}
RenderOverlay(gameType, map.Theater, topLeft, tileSize, tileScale, overlay).Item2(graphics);
RenderOverlay(gameType, topLeft, tileSize, tileScale, overlay).Item2(graphics);
}
}
@ -348,7 +348,7 @@ namespace MobiusEditor.Render
{
continue;
}
RenderWaypoint(gameType, map.BasicSection.SoloMission, map.Theater, tileSize, flagColors, waypoint).Item2(graphics);
RenderWaypoint(gameType, map.BasicSection.SoloMission, tileSize, flagColors, waypoint).Item2(graphics);
}
}
}
@ -375,7 +375,7 @@ namespace MobiusEditor.Render
);
imageAttributes.SetColorMatrix(colorMatrix);
}
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, smudge.Type.Name, smudge.Icon, out Tile tile))
if (Globals.TheTilesetManager.GetTileData(smudge.Type.Name, smudge.Icon, out Tile tile))
{
Rectangle smudgeBounds = RenderBounds(tile.Image.Size, new Size(1, 1), tileScale);
smudgeBounds.X += topLeft.X * tileSize.Width;
@ -393,14 +393,14 @@ namespace MobiusEditor.Render
}
}
public static (Rectangle, Action<Graphics>) RenderOverlay(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, double tileScale, Overlay overlay)
public static (Rectangle, Action<Graphics>) RenderOverlay(GameType gameType, Point topLeft, Size tileSize, double tileScale, Overlay overlay)
{
OverlayType ovtype = overlay.Type;
string name = ovtype.GraphicsSource;
int icon = ovtype.IsConcrete || ovtype.IsResource || ovtype.IsWall || ovtype.ForceTileNr == -1 ? overlay.Icon : ovtype.ForceTileNr;
bool isTeleport = gameType == GameType.SoleSurvivor && ovtype == SoleSurvivor.OverlayTypes.Teleport && Globals.AdjustSoleTeleports;
// For Decoration types, generate dummy if not found.
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, name, icon, out Tile tile, (ovtype.Flag & OverlayTypeFlag.Pavement) != 0, false))
if (Globals.TheTilesetManager.GetTileData(name, icon, out Tile tile, (ovtype.Flag & OverlayTypeFlag.Pavement) != 0, false))
{
int actualTopLeftX = topLeft.X * tileSize.Width;
int actualTopLeftY = topLeft.Y * tileSize.Height;
@ -456,7 +456,7 @@ namespace MobiusEditor.Render
public static (Rectangle, Action<Graphics>, bool) RenderTerrain(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, double tileScale, Terrain terrain)
{
string tileName = terrain.Type.GraphicsSource;
if (!Globals.TheTilesetManager.GetTileData(theater.Tilesets, tileName, terrain.Type.DisplayIcon, out Tile tile))
if (!Globals.TheTilesetManager.GetTileData(tileName, terrain.Type.DisplayIcon, out Tile tile))
{
Debug.Print(string.Format("Terrain {0} ({1}) not found", tileName, terrain.Type.DisplayIcon));
return (Rectangle.Empty, (g) => { }, false);
@ -488,7 +488,7 @@ namespace MobiusEditor.Render
return (terrainBounds, render, false);
}
public static (Rectangle, Action<Graphics>, bool) RenderBuilding(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, double tileScale, Building building)
public static (Rectangle, Action<Graphics>, bool) RenderBuilding(GameType gameType, Point topLeft, Size tileSize, double tileScale, Building building)
{
var tint = building.Tint;
var icon = building.Type.FrameOFfset;
@ -502,7 +502,7 @@ namespace MobiusEditor.Render
// Only fetch if damaged. BuildingType.IsSingleFrame is an override for the RA mines. Everything else works with one simple logic.
if (isDamaged && !building.Type.IsSingleFrame)
{
maxIcon = Globals.TheTilesetManager.GetTileDataLength(theater.Tilesets, building.Type.GraphicsSource);
maxIcon = Globals.TheTilesetManager.GetTileDataLength(building.Type.GraphicsSource);
hasCollapseFrame = (gameType == GameType.TiberianDawn || gameType == GameType.SoleSurvivor) && maxIcon > 1 && maxIcon % 2 == 1;
damageIconOffs = maxIcon / 2;
collapseIcon = maxIcon - 1;
@ -527,7 +527,7 @@ namespace MobiusEditor.Render
}
}
ITeamColor tc = building.Type.CanRemap ? Globals.TheTeamColorManager[building.House.BuildingTeamColor] : null;
if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.GraphicsSource, icon, tc, out Tile tile))
if (Globals.TheTilesetManager.GetTeamColorTileData(building.Type.GraphicsSource, icon, tc, out Tile tile))
{
var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
var maxSize = new Size(building.Type.Size.Width * tileSize.Width, building.Type.Size.Height * tileSize.Height);
@ -540,10 +540,10 @@ namespace MobiusEditor.Render
int overlayIcon = 0;
if (building.Strength <= healthyMin)
{
int maxOverlayIcon = Globals.TheTilesetManager.GetTileDataLength(theater.Tilesets, building.Type.FactoryOverlay);
int maxOverlayIcon = Globals.TheTilesetManager.GetTileDataLength(building.Type.FactoryOverlay);
overlayIcon = maxOverlayIcon / 2;
}
Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.FactoryOverlay, overlayIcon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out factoryOverlayTile);
Globals.TheTilesetManager.GetTeamColorTileData(building.Type.FactoryOverlay, overlayIcon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out factoryOverlayTile);
}
void render(Graphics g)
{
@ -594,11 +594,11 @@ namespace MobiusEditor.Render
}
}
public static (Rectangle, Action<Graphics>, bool) RenderInfantry(TheaterType theater, Point topLeft, Size tileSize, Infantry infantry, InfantryStoppingType infantryStoppingType)
public static (Rectangle, Action<Graphics>, bool) RenderInfantry(Point topLeft, Size tileSize, Infantry infantry, InfantryStoppingType infantryStoppingType)
{
var icon = HumanShape[Facing32[infantry.Direction.ID]];
string teamColor = infantry.House?.UnitTeamColor;
if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, infantry.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile))
if (Globals.TheTilesetManager.GetTeamColorTileData(infantry.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile))
{
// These values are experimental, from comparing map editor screenshots to game screenshots. -Nyer
int infantryCorrectX = tileSize.Width / -12;
@ -665,7 +665,7 @@ namespace MobiusEditor.Render
}
}
public static (Rectangle, Action<Graphics>, bool) RenderUnit(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, Unit unit)
public static (Rectangle, Action<Graphics>, bool) RenderUnit(GameType gameType, Point topLeft, Size tileSize, Unit unit)
{
int icon = -1;
if (gameType == GameType.TiberianDawn || gameType == GameType.SoleSurvivor)
@ -732,7 +732,7 @@ namespace MobiusEditor.Render
teamColor = unit.House.UnitTeamColor;
}
}
if (!Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, unit.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile))
if (!Globals.TheTilesetManager.GetTeamColorTileData(unit.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile))
{
Debug.Print(string.Format("Unit {0} ({1}) not found", unit.Type.Name, icon));
return (Rectangle.Empty, (g) => { }, false);
@ -795,9 +795,9 @@ namespace MobiusEditor.Render
turret2Icon = getRotorIcon(turret2Name, unit.Direction.ID, turret2Icon);
}
if (turretName != null)
Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, turretName, turretIcon, Globals.TheTeamColorManager[teamColor], out turretTile);
Globals.TheTilesetManager.GetTeamColorTileData(turretName, turretIcon, Globals.TheTeamColorManager[teamColor], out turretTile);
if (turret2Name != null)
Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, turret2Name, turret2Icon, Globals.TheTeamColorManager[teamColor], out turret2Tile);
Globals.TheTilesetManager.GetTeamColorTileData(turret2Name, turret2Icon, Globals.TheTeamColorManager[teamColor], out turret2Tile);
}
var tint = unit.Tint;
void render(Graphics g)
@ -903,16 +903,16 @@ namespace MobiusEditor.Render
return (renderBounds, render, false);
}
public static (Rectangle, Action<Graphics>) RenderWaypoint(GameType gameType, bool soloMission, TheaterType theater, Size tileSize, ITeamColor[] flagColors, Waypoint waypoint)
public static (Rectangle, Action<Graphics>) RenderWaypoint(GameType gameType, bool soloMission, Size tileSize, ITeamColor[] flagColors, Waypoint waypoint)
{
// Opacity is normally 0.5 for non-flag waypoint indicators, but is variable because the post-render
// actions of the waypoints tool will paint a fully opaque version over the currently selected waypoint.
//int mpId = Waypoint.GetMpIdFromFlag(waypoint.Flag);
//float defaultOpacity = !soloMission && mpId >= 0 && mpId < flagColors.Length ? 1.0f : 0.5f;
return Render(gameType, soloMission, theater, tileSize, flagColors, waypoint, 0.5f);
return RenderWaypoint(gameType, soloMission, tileSize, flagColors, waypoint, 0.5f);
}
public static (Rectangle, Action<Graphics>) Render(GameType gameType, bool soloMission, TheaterType theater, Size tileSize, ITeamColor[] flagColors, Waypoint waypoint, float transparencyModifier)
public static (Rectangle, Action<Graphics>) RenderWaypoint(GameType gameType, bool soloMission, Size tileSize, ITeamColor[] flagColors, Waypoint waypoint, float transparencyModifier)
{
if (!waypoint.Point.HasValue)
{
@ -925,31 +925,36 @@ namespace MobiusEditor.Render
float brightness = 1.0f;
int mpId = Waypoint.GetMpIdFromFlag(waypoint.Flag);
int icon = 0;
bool defaultIcon = true;
bool isDefaultIcon = true;
bool gotIcon = false;
Tile tile;
if (!soloMission && mpId >= 0 && mpId < flagColors.Length)
{
defaultIcon = false;
isDefaultIcon = false;
tileGraphics = "flagfly";
// Always paint flags as opaque.
transparencyModifier = 1.0f;
teamColor = flagColors[mpId];
icon = 0;
gotIcon = Globals.TheTilesetManager.GetTeamColorTileData(tileGraphics, icon, teamColor, out tile);
}
if (gameType == GameType.SoleSurvivor && (waypoint.Flag & WaypointFlag.CrateSpawn) == WaypointFlag.CrateSpawn)
else if (gameType == GameType.SoleSurvivor && (waypoint.Flag & WaypointFlag.CrateSpawn) == WaypointFlag.CrateSpawn)
{
defaultIcon = false;
isDefaultIcon = false;
tileGraphics = "scrate";
icon = 0;
//tint = Color.FromArgb(waypoint.Tint.A, Color.Green);
//brightness = 1.5f;
gotIcon = Globals.TheTilesetManager.GetTileData(tileGraphics, icon, out tile);
}
bool gotIcon = Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, tileGraphics, icon, teamColor, out Tile tile, true, true);
if (!gotIcon && defaultIcon)
else
{
gotIcon = Globals.TheTilesetManager.GetTileData(tileGraphics, icon, out tile);
}
if (!gotIcon && isDefaultIcon)
{
// Beacon only exists in remastered graphics. Get fallback.
tileGraphics = "armor";
icon = 6;
gotIcon = Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, tileGraphics, icon, teamColor, out tile, true, true);
gotIcon = Globals.TheTilesetManager.GetTeamColorTileData(tileGraphics, icon, teamColor, out tile);
}
if (!gotIcon)
{
@ -1184,7 +1189,7 @@ namespace MobiusEditor.Render
string name = ovtype.GraphicsSource;
int icon = ovtype.IsConcrete || ovtype.IsResource || ovtype.IsWall || ovtype.ForceTileNr == -1 ? overlay.Icon : ovtype.ForceTileNr;
// For Decoration types, generate dummy if not found.
if (!Globals.TheTilesetManager.GetTileData(theater.Tilesets, name, icon, out Tile tile, (ovtype.Flag & OverlayTypeFlag.Pavement) != 0, false))
if (!Globals.TheTilesetManager.GetTileData(name, icon, out Tile tile, (ovtype.Flag & OverlayTypeFlag.Pavement) != 0, false))
{
return null;
}
@ -1306,7 +1311,7 @@ namespace MobiusEditor.Render
Type = SoleSurvivor.OverlayTypes.Road,
Tint = Color.FromArgb(128, Color.White)
};
RenderOverlay(gameType, map.Theater, p, tileSize, tileScale, footballTerrain).Item2(graphics);
RenderOverlay(gameType, p, tileSize, tileScale, footballTerrain).Item2(graphics);
}
}
@ -1328,7 +1333,7 @@ namespace MobiusEditor.Render
ITeamColor[] flagColors = map.FlagColors.ToArray();
foreach (Waypoint wp in footballWayPoints)
{
RenderWaypoint(gameType, false, map.Theater, tileSize, flagColors, wp).Item2(graphics);
RenderWaypoint(gameType, false, tileSize, flagColors, wp).Item2(graphics);
}
}

View File

@ -486,8 +486,8 @@ namespace MobiusEditor.TiberianDawn
{
throw new ApplicationException("Cannot find the necessary files inside the " + Path.GetFileName(path) + " archive.");
}
using (BinaryReader iniReader = new BinaryReader(megafile.Open(iniFile)))
using (BinaryReader binReader = new BinaryReader(megafile.Open(binFile)))
using (BinaryReader iniReader = new BinaryReader(megafile.OpenFile(iniFile)))
using (BinaryReader binReader = new BinaryReader(megafile.OpenFile(binFile)))
{
iniBytes = iniReader.ReadAllBytes();
ParseIniContent(ini, iniBytes, forSole);

View File

@ -736,7 +736,7 @@ namespace MobiusEditor.Tools
bibRender.Add(bibCellRender);
}
}
var renderBuilding = MapRenderer.RenderBuilding(plugin.GameType, map.Theater, new Point(0, 0), Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding);
var renderBuilding = MapRenderer.RenderBuilding(plugin.GameType, new Point(0, 0), Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding);
Size previewSize = mockBuilding.OverlapBounds.Size;
var buildingPreview = new Bitmap(previewSize.Width * Globals.PreviewTileWidth, previewSize.Height * Globals.PreviewTileHeight);
buildingPreview.SetResolution(96, 96);

View File

@ -732,7 +732,7 @@ namespace MobiusEditor.Tools
using (var g = Graphics.FromImage(infantryPreview))
{
MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale);
MapRenderer.RenderInfantry(map.Theater, Point.Empty, Globals.PreviewTileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
MapRenderer.RenderInfantry(Point.Empty, Globals.PreviewTileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
}
infantryTypeMapPanel.MapImage = infantryPreview;
}

View File

@ -351,7 +351,7 @@ namespace MobiusEditor.Tools
Type = SelectedOverlayType,
Icon = 0
};
var render = MapRenderer.RenderOverlay(plugin.GameType, map.Theater, new Point(0,0), Globals.PreviewTileSize, Globals.PreviewTileScale, mockOverlay);
var render = MapRenderer.RenderOverlay(plugin.GameType, new Point(0,0), Globals.PreviewTileSize, Globals.PreviewTileScale, mockOverlay);
if (!render.Item1.IsEmpty)
{
using (var g = Graphics.FromImage(overlayPreview))

View File

@ -551,7 +551,7 @@ namespace MobiusEditor.Tools
using (var g = Graphics.FromImage(unitPreview))
{
MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale);
MapRenderer.RenderUnit(plugin.GameType, map.Theater, new Point(1, 1), Globals.PreviewTileSize, mockUnit).Item2(g);
MapRenderer.RenderUnit(plugin.GameType, new Point(1, 1), Globals.PreviewTileSize, mockUnit).Item2(g);
}
unitTypeMapPanel.MapImage = unitPreview;
}

View File

@ -464,7 +464,7 @@ namespace MobiusEditor.Tools
// If the selected waypoint is not a flag, re-render it as opaque.
if (selected != null && (plugin.Map.BasicSection.SoloMission || (selected.Flag & WaypointFlag.PlayerStart) != WaypointFlag.PlayerStart))
{
MapRenderer.Render(plugin.GameType, true, map.Theater, Globals.MapTileSize, map.FlagColors.ToArray(), selected, 1.0f).Item2(graphics);
MapRenderer.RenderWaypoint(plugin.GameType, true, Globals.MapTileSize, map.FlagColors.ToArray(), selected, 1.0f).Item2(graphics);
}
// Render those here to they are put over the opaque redraw of the current waypoint.
MapRenderer.RenderAllTechnoTriggers(graphics, plugin.Map, Globals.MapTileSize, Globals.MapTileScale, Layers);

View File

@ -65,7 +65,7 @@ namespace MobiusEditor.Utility
string testIniFile = megafile.Where(p => ext.IsMatch(Path.GetExtension(p).ToLower())).FirstOrDefault();
if (testIniFile != null)
{
using (StreamReader iniReader = new StreamReader(megafile.Open(testIniFile), encDOS))
using (StreamReader iniReader = new StreamReader(megafile.OpenFile(testIniFile), encDOS))
{
iniContents = iniReader.ReadToEnd();
}

View File

@ -14,7 +14,7 @@
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
namespace MobiusEditor.Utility
namespace MobiusEditor.Utility.Hashing
{
public class CRC
{

View File

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MobiusEditor.Utility.Hashing
{
public class HashRol1 : Rol
{
public override UInt32 GetNameIdCorrectCase(String name)
{
return GetNameId(name, 1);
}
public override UInt32 GetNameIdCorrectCase(Byte[] data)
{
return GetNameId(data, 1);
}
public override String GetDisplayName()
{
return "ROL (TD/RA)";
}
public override String GetSimpleName()
{
return "ROL";
}
}
public class HashRol3 : Rol
{
public override UInt32 GetNameIdCorrectCase(String name)
{
return GetNameId(name, 3);
}
public override UInt32 GetNameIdCorrectCase(Byte[] data)
{
return GetNameId(data, 3);
}
public override String GetDisplayName()
{
return "ROL3 (setup TS/RA2/...)";
}
public override String GetSimpleName()
{
return "ROL3";
}
}
public abstract class Rol
{
public UInt32 GetNameId(String name)
{
if (name == null)
name = String.Empty;
return GetNameIdCorrectCase(this.NeedsUpperCase ? name.ToUpperInvariant() : name);
}
/// <summary>
/// The hashing method. Assumes that the input is already formatted to the correct case according to NeedsUpperCase.
/// </summary>
/// <param name="name">String to hash.</param>
/// <returns>The hashed value.</returns>
public abstract UInt32 GetNameIdCorrectCase(String name);
/// <summary>
/// The hashing method. Assumes that the input is already formatted to the correct case according to NeedsUpperCase.
/// </summary>
/// <param name="name">String to hash, as byte array.</param>
/// <returns>The hashed value.</returns>
public abstract UInt32 GetNameIdCorrectCase(Byte[] data);
/// <summary>
/// Returns the display name of the hashing method.
/// </summary>
/// <returns>The display name of the hashing method.</returns>
public abstract String GetDisplayName();
/// <summary>
/// Returns the short name of the hashing method.
/// </summary>
/// <returns>The short name of the hashing method.</returns>
public abstract String GetSimpleName();
/// <summary>
/// Allows supporting methods that are not case insensitive.
/// </summary>
public virtual Boolean NeedsUpperCase
{
get { return true; }
}
protected UInt32 GetNameId(String name, Int32 rot)
{
return GetNameId(Encoding.ASCII.GetBytes(name), rot);
}
protected UInt32 GetNameId(Byte[] values, Int32 rot)
{
Int32 i = 0;
UInt32 id = 0;
Int32 len = values.Length; // length of the filename
while (i < len)
{
// get next uint32 chunk
UInt32 buffer = this.GetUInt32FromBuffer(values, len, ref i);
if (i <= len)
id = RotateLeft(id, rot) + buffer;
else // Known quirk: final one only does ROL-1
id = RotateLeft(id, 1) + buffer;
}
return id;
}
protected UInt32 GetUInt32FromBuffer(Byte[] values, Int32 length, ref Int32 index)
{
UInt32 a = 0;
for (Int32 i = 0; i < 4; ++i)
{
a >>= 8;
if (index < length)
a += ((UInt32)values[index] << 24);
index++;
}
return a;
}
public static UInt32 RotateLeft(UInt32 value, Int32 count)
{
return (value << count) | (value >> (32 - count));
}
public static UInt32 RotateRight(UInt32 value, Int32 count)
{
return (value >> count) | (value << (32 - count));
}
}
}

View File

@ -97,18 +97,25 @@ namespace MobiusEditor.Utility
}
}
public Stream Open(string path)
public Stream OpenFile(string path)
{
if (disposedValue)
{
throw new ObjectDisposedException("MegaFile");
}
if (!fileTable.TryGetValue(path, out SubFileData subFile))
{
return null;
}
return megafileMap.CreateViewStream(subFile.SubfileImageDataOffset, subFile.SubfileSize, MemoryMappedFileAccess.Read);
}
public IEnumerator<string> GetEnumerator()
{
if (disposedValue)
{
throw new ObjectDisposedException("MegaFile");
}
foreach (var file in stringTable)
{
yield return file;

View File

@ -18,6 +18,7 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using MobiusEditor.Utility.Hashing;
namespace MobiusEditor.Utility
{

View File

@ -12,11 +12,12 @@
// 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.Interface;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using MobiusEditor.Interface;
using MobiusEditor.Model;
namespace MobiusEditor.Utility
{
@ -38,7 +39,7 @@ namespace MobiusEditor.Utility
this.LoadRoot = Path.GetFullPath(loadRoot);
}
public bool LoadArchive(GameType gameType, string archivePath)
public bool LoadArchive(string archivePath)
{
if (!Path.IsPathRooted(archivePath))
{
@ -89,7 +90,7 @@ namespace MobiusEditor.Utility
}
foreach (var megafile in megafiles)
{
var stream = megafile.Open(path.ToUpper());
var stream = megafile.OpenFile(path.ToUpper());
if (stream != null)
{
return stream;
@ -98,7 +99,7 @@ namespace MobiusEditor.Utility
return null;
}
public void Reset(GameType gameType)
public void Reset(GameType gameType, TheaterType theater)
{
// Do nothing.
}
@ -110,7 +111,7 @@ namespace MobiusEditor.Utility
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
return this.GetEnumerator();
}
#region IDisposable Support

View File

@ -0,0 +1,200 @@
using MobiusEditor.Utility.Hashing;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
namespace MobiusEditor.Utility
{
public class Mixfile: IDisposable
{
private Dictionary<uint, (uint Offset, uint Length)> mixFileContents = new Dictionary<uint, (uint, uint)>();
private HashRol1 hashRol = new HashRol1();
public string MixFileName { get; private set; }
private MemoryMappedFile mixFileMap;
private bool isEmbedded = false;
private long fileStart;
private long fileLength;
public Mixfile(string mixPath)
{
FileInfo mixFile = new FileInfo(mixPath);
this.fileStart = 0;
this.fileLength = mixFile.Length;
this.MixFileName = mixPath;
this.mixFileMap = MemoryMappedFile.CreateFromFile(
new FileStream(mixPath, FileMode.Open, FileAccess.Read, FileShare.Read),
null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, false);
this.ReadMixHeader(this.mixFileMap, this.fileStart, this.fileLength);
}
public Mixfile(Mixfile container, string name)
{
this.isEmbedded = true;
this.MixFileName = container.MixFileName + " -> " + name;
if (!container.GetFileInfo(name, out uint offset, out uint length))
{
throw new FileNotFoundException(name + " was not found inside this mix archive.");
}
this.fileStart = offset;
this.fileLength = length;
// Copy reference to parent map. The "CreateViewStream" function takes care of reading the right parts from it.
this.mixFileMap = container.mixFileMap;
this.ReadMixHeader(mixFileMap, offset, fileLength);
}
private void ReadMixHeader(MemoryMappedFile mixMap, long mixStart, long mixLength)
{
mixFileContents.Clear();
uint readOffset = 0;
ushort nrOfFiles = 0;
bool hasFlags = false;
bool encrypted = false;
bool checksum = false;
using (BinaryReader headerReader = new BinaryReader(CreateViewStream(mixMap, mixStart, mixLength, readOffset, 2)))
{
ushort start = headerReader.ReadUInt16();
if (start == 0)
hasFlags = true;
else
nrOfFiles = start;
readOffset += 2;
}
if (hasFlags)
{
using (BinaryReader headerReader = new BinaryReader(CreateViewStream(mixMap, mixStart, mixLength, readOffset, 2)))
{
var flags = headerReader.ReadUInt16();
checksum = (flags & 1) != 0;
encrypted = (flags & 2) != 0;
readOffset += 2;
}
// Not encrypted; read nr of files.
if (!encrypted)
{
using (BinaryReader headerReader = new BinaryReader(CreateViewStream(mixMap, mixStart, mixLength, readOffset, 2)))
{
nrOfFiles = headerReader.ReadUInt16();
readOffset += 2;
}
}
}
uint headerSize;
Byte[] header = null;
if (encrypted)
{
using (BinaryReader headerReader = new BinaryReader(CreateViewStream(mixMap, mixStart, mixLength, readOffset, 80)))
{
byte[] blowfishKey = headerReader.ReadAllBytes();
readOffset += 80;
}
// The rest of the blowfish decryption
throw new NotSupportedException("Encrypred mix archives are currently not supported.");
}
else
{
// Ignore data size in header; it's only used for caching.
readOffset += 2;
headerSize = (UInt32)(nrOfFiles * 12);
if (readOffset + headerSize > mixLength)
{
throw new ArgumentOutOfRangeException("Not a valid mix file: header length exceeds file length.");
}
using (BinaryReader headerReader = new BinaryReader(CreateViewStream(mixMap, mixStart, mixLength, readOffset, headerSize)))
{
header = headerReader.ReadBytes((Int32)headerSize);
// End of header reading; no longer needed.
//readOffset += headerSize;
}
}
for (int i = 0; i < nrOfFiles; ++i)
{
using (BinaryReader headerReader = new BinaryReader(new MemoryStream(header)))
{
uint fileId = headerReader.ReadUInt32();
uint fileOffset = headerReader.ReadUInt32();
uint fileLength = headerReader.ReadUInt32();
if (fileOffset + fileLength > mixLength)
{
throw new ArgumentOutOfRangeException(String.Format("Not a valid mix file: file #{0} with id {1:X08} exceeds archive length.", i, fileId));
}
mixFileContents.Add(fileId, (fileOffset, fileLength));
}
}
}
public bool GetFileInfo(string filename, out uint offset, out uint length)
{
offset = 0;
length = 0;
uint fileId = hashRol.GetNameId(filename);
(uint Offset, uint Length) fileLoc;
if (!mixFileContents.TryGetValue(fileId, out fileLoc))
{
return false;
}
offset = fileLoc.Offset;
length = fileLoc.Length;
return true;
}
public Stream OpenFile(string path)
{
uint fileId = hashRol.GetNameId(path);
(uint Offset, uint Length) fileLoc;
if (!mixFileContents.TryGetValue(fileId, out fileLoc))
{
return null;
}
return CreateViewStream(mixFileMap, fileStart, fileLength, fileLoc.Offset, fileLoc.Length);
}
/// <summary>
/// Creates a view stream on the current mix file, or on the current embedded mix file.
/// </summary>
/// <param name="mixMap">The MemoryMappedFile of the mix file or parent mix file</param>
/// <param name="mixFileStart">Start of the current mix file inside the mixMap</param>
/// <param name="mixFileLength">End of the current mix file inside the mixMap</param>
/// <param name="dataReadOffset">Read position inside the current mix file.</param>
/// <param name="dataReadLength">Length of the data to read.</param>
/// <returns></returns>
/// <exception cref="IndexOutOfRangeException">The data is not in the bounds of this mix file.</exception>
private Stream CreateViewStream(MemoryMappedFile mixMap, long mixFileStart, long mixFileLength, uint dataReadOffset, uint dataReadLength)
{
if (disposedValue)
{
throw new ObjectDisposedException("Mixfile");
}
if (dataReadOffset + dataReadLength > mixFileLength)
{
throw new IndexOutOfRangeException("Data exceeds mix file bounds.");
}
return mixMap.CreateViewStream(mixFileStart + dataReadOffset, dataReadLength, MemoryMappedFileAccess.Read);
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
// Only dispose if not an embedded mix file.
// If embedded, the mixFileMap is contained in the parent.
if (disposing && !isEmbedded)
{
mixFileMap.Dispose();
}
mixFileMap = null;
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}

View File

@ -0,0 +1,263 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MobiusEditor.Interface;
using MobiusEditor.Model;
namespace MobiusEditor.Utility
{
public class MixfileManager : IArchiveManager
{
private class MixInfo
{
public string Name { get; set; }
public bool IsContainer { get; set; }
public bool CanBeEmbedded { get; set; }
public bool IsTheater { get; set; }
public MixInfo(String name, bool isContainer, bool canBeEmbedded, bool isTheater)
{
this.Name = name;
this.IsContainer = isContainer;
this.CanBeEmbedded = canBeEmbedded;
this.IsTheater = isTheater;
}
}
private string applicationPath;
private Dictionary<GameType, string> gameFolders;
private Dictionary<GameType, List<MixInfo>> gameArchives;
private readonly List<MixInfo> currentMixFileInfo = new List<MixInfo>();
private List<string> currentMixNames = new List<string>();
private readonly Dictionary<string, Mixfile> currentMixFiles = new Dictionary<string, Mixfile>();
private GameType currentGameType = GameType.None;
public string LoadRoot { get { return applicationPath; } }
public string[] ExpandModPaths { get; set; }
public MixfileManager(String applicationPath, Dictionary<GameType, String> gameFolders)
{
this.applicationPath = applicationPath;
this.gameFolders = gameFolders;
this.gameArchives = new Dictionary<GameType, List<MixInfo>>();
//this.Reset(currentGameType, null);
}
public bool FileExists(String path)
{
// TODO check mod paths first? Not sure; files tend to need mixfiles
using (Stream str = this.OpenFile(path))
{
return str != null;
}
}
public bool LoadArchive(GameType gameType, String archivePath, bool isContainer, bool canBeEmbedded)
{
return LoadArchive(gameType, archivePath, isContainer, canBeEmbedded, false);
}
public bool LoadArchive(GameType gameType, String archivePath, bool isContainer, bool canBeEmbedded, bool isTheater)
{
// Doesn't really 'load' the archive, but instead registers it as known filename for this game type.
// The actual loading won't happen until a Reset(...) is executed to specify the game to initialise.
if (!gameFolders.TryGetValue(gameType, out string gamePath))
{
return false;
}
List<MixInfo> archivesForGame;
if (!gameArchives.TryGetValue(gameType, out archivesForGame))
{
archivesForGame = new List<MixInfo>();
gameArchives[gameType] = archivesForGame;
}
// Not using hash map since order and iteration will be important.
if (archivesForGame.FindIndex(info => String.Equals(info.Name, archivePath, StringComparison.InvariantCultureIgnoreCase)) == -1)
{
archivesForGame.Add(new MixInfo(archivePath, isContainer, canBeEmbedded, isTheater));
}
String fullPath = Path.Combine(applicationPath, gamePath, archivePath);
// Mod paths might still add it, but this initial check is returned.
return File.Exists(fullPath);
}
public Stream OpenFile(String path)
{
// Game folders dictionary determines which games are "known" to the system.
if (!gameFolders.TryGetValue(currentGameType, out string gamePath))
{
return null;
}
// 1. Loose files in game files path
string loosePath = Path.Combine(gamePath, path);
if (File.Exists(loosePath))
{
return File.Open(loosePath, FileMode.Open, FileAccess.Read);
}
// 2. Loose files in mod path
if (ExpandModPaths != null && ExpandModPaths.Length > 0)
{
foreach (string modFilePath in ExpandModPaths)
{
string modPath = Path.Combine(modFilePath, "ccdata", path);
if (File.Exists(modPath))
{
return File.Open(modPath, FileMode.Open, FileAccess.Read);
}
}
}
// 3. Contained inside mix files. Note that this automatically takes mods and
// embedded mix files into account, since they are loaded in the Reset function.
foreach (MixInfo mixInfo in currentMixFileInfo)
{
if (currentMixFiles.TryGetValue(mixInfo.Name, out Mixfile archive))
{
Stream stream = archive.OpenFile(path);
if (stream != null)
{
return stream;
}
}
}
return null;
}
public void Reset(GameType gameType, TheaterType theater)
{
String theaterMixFile = theater == null ? null : theater.ClassicTileset + ".mix";
// Clean up previously loaded files.
foreach (Mixfile oldMixFile in currentMixFiles.Values)
{
try
{
oldMixFile.Dispose();
}
catch { /* ignore */ }
}
currentMixFiles.Clear();
currentMixFileInfo.Clear();
currentMixNames = new List<string>();
this.currentGameType = GameType.None;
// Load current files
// Game folders dictionary determines which games are "known" to the system.
if (!gameFolders.TryGetValue(gameType, out string gamePath))
{
return;
}
List<MixInfo> newMixFileInfo = gameArchives.Where(kv => kv.Key == gameType).SelectMany(kv => kv.Value).ToList();
Dictionary<string, Mixfile> newMixFiles = new Dictionary<string, Mixfile>();
if (ExpandModPaths != null && ExpandModPaths.Length > 0)
{
// In each mod folder, try to read all mix files.
foreach (string modPath in ExpandModPaths)
{
foreach (MixInfo mixInfo in newMixFileInfo)
{
if (mixInfo.IsTheater && !String.Equals(theaterMixFile, mixInfo.Name, StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
String mixPath = Path.Combine(modPath, "ccdata");
// This automatically excludes already-loaded files.
this.AddMixFileIfPresent(newMixFiles, newMixFileInfo, mixInfo, mixPath);
}
}
}
foreach (MixInfo mixInfo in newMixFileInfo)
{
if (mixInfo.IsTheater && !String.Equals(theaterMixFile, mixInfo.Name, StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
String mixPath = Path.Combine(gamePath, "ccdata");
// This automatically excludes already-loaded files.
this.AddMixFileIfPresent(newMixFiles, newMixFileInfo, mixInfo, mixPath);
}
this.currentGameType = gameType;
currentMixFiles.Clear();
currentMixFiles.Union(newMixFiles);
currentMixFileInfo.Clear();
currentMixFileInfo.AddRange(newMixFileInfo);
currentMixNames = currentMixFileInfo.Select(info => info.Name).ToList();
}
private bool AddMixFileIfPresent(Dictionary<String, Mixfile> readMixFiles, List<MixInfo> readMixNames, MixInfo mixToAdd, string readFolder)
{
// 1. Look for file in given folder
// 2. if 'canBeEmbedded', look for file inside archives inside mix files list
// 3. If found in either, add to list of read mix files
//if (File.Exists(newMixPath)) { }
string mixName = mixToAdd.Name;
if (readMixFiles.ContainsKey(mixName))
{
return false;
}
string localPath = Path.Combine(readFolder, mixName);
Mixfile mixFile = null;
if (File.Exists(localPath))
{
try
{
mixFile = new Mixfile(localPath);
}
catch
{
return false;
}
}
if (mixFile == null && mixToAdd.CanBeEmbedded)
{
foreach (MixInfo readArchive in readMixNames)
{
if (readArchive.IsContainer && readMixFiles.TryGetValue(readArchive.Name, out Mixfile container))
{
// Check if file exists
if (container.GetFileInfo(mixName, out _, out _))
{
// Create as embedded mix file
mixFile = new Mixfile(container, mixName);
}
}
}
}
if (mixFile != null)
{
readMixFiles.Add(mixName, mixFile);
}
return mixFile != null;
}
public IEnumerator<string> GetEnumerator()
{
return currentMixNames.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
//megafiles.ForEach(m => m.Dispose());
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}

View File

@ -28,9 +28,8 @@ using MobiusEditor.Model;
namespace MobiusEditor.Utility
{
public class TextureManager
public class TextureManager: IDisposable
{
private static string MissingTexture = "DATA\\ART\\TEXTURES\\SRGB\\COMMON\\MISC\\MISSING.TGA";
private bool processedMissingTexture = false;
@ -43,26 +42,24 @@ namespace MobiusEditor.Utility
this.megafileManager = megafileManager;
}
public void Reset(GameType gameType, TheaterType theater)
public void Reset()
{
Bitmap[] cachedImages = cachedTextures.Values.ToArray();
cachedTextures.Clear();
// Bitmaps need to be specifically disposed.
for (int i = 0; i < cachedImages.Length; ++i)
{
try
{
cachedImages[i].Dispose();
}
catch
{
// Ignore.
}
try { cachedImages[i].Dispose(); }
catch { /* Ignore */ }
}
}
public (Bitmap, Rectangle) GetTexture(string filename, ITeamColor teamColor, bool generateFallback)
{
if (disposedValue)
{
throw new ObjectDisposedException("TextureManager");
}
if (!cachedTextures.ContainsKey(filename) && generateFallback)
{
(Bitmap bm, Rectangle bounds) = GetDummyImage();
@ -332,5 +329,26 @@ namespace MobiusEditor.Utility
}
return (bm, r);
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
this.Reset();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}

View File

@ -13,6 +13,8 @@
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -20,7 +22,7 @@ using System.Xml;
namespace MobiusEditor.Utility
{
public class TilesetManager
public class TilesetManager: ITilesetManager, IDisposable
{
private readonly Dictionary<string, Tileset> tilesets = new Dictionary<string, Tileset>();
@ -28,6 +30,9 @@ namespace MobiusEditor.Utility
private readonly TextureManager textureManager;
private readonly string xmlPath;
private readonly string texturesPath;
private TheaterType theater;
public TextureManager TextureManager { get { return textureManager; } }
public TilesetManager(IArchiveManager megafileManager, TextureManager textureManager, string xmlPath, string texturesPath)
{
@ -77,22 +82,33 @@ namespace MobiusEditor.Utility
}
}
public void Reset()
public void Reset(TheaterType theater)
{
this.textureManager.Reset();
foreach (var item in tilesets)
{
item.Value.Reset();
}
LoadXmlfiles();
this.theater = theater;
}
public bool GetTeamColorTileData(IEnumerable<string> searchTilesets, string name, int shape, ITeamColor teamColor, out int fps, out Tile[] tiles, bool generateFallback, bool onlyIfDefined)
private bool GetTeamColorTileData(string name, int shape, ITeamColor teamColor, out int fps, out Tile[] tiles, bool generateFallback, bool onlyIfDefined)
{
if (disposedValue)
{
throw new ObjectDisposedException("TilesetManager");
}
fps = 0;
tiles = null;
Tileset first = null;
// Tilesets are now searched in the given order, allowing accurate defining of main tilesets and fallback tilesets.
//foreach (var tileset in tilesets.Join(searchTilesets, x => x.Key, y => y, (x, y) => x.Value))
if (this.theater == null)
{
return false;
}
IEnumerable<string> searchTilesets = this.theater.Tilesets;
foreach (string searchTileset in searchTilesets)
{
if (!tilesets.ContainsKey(searchTileset))
@ -111,7 +127,8 @@ namespace MobiusEditor.Utility
// Tile found, but contains no data. Re-fetch with dummy generation.
if (tileset.GetTileData(name, shape, teamColor, out fps, out tiles, true))
{
return true;
// Signal in return value that dummy was generated.
return false;
}
continue;
}
@ -121,85 +138,47 @@ namespace MobiusEditor.Utility
// If the tile is not defined at all, and onlyifdefined is not enabled, make a dummy entry anyway.
if (!onlyIfDefined && generateFallback && first != null && first.GetTileData(name, shape, teamColor, out fps, out tiles, true))
{
return true;
// Signal in return value that dummy was generated.
return false;
}
return false;
}
public bool GetTeamColorTileData(IEnumerable<string> searchTilesets, string name, int shape, ITeamColor teamColor, out int fps, out Tile tile, bool generateFallback, bool onlyIfDefined)
private bool GetTeamColorTileData(string name, int shape, ITeamColor teamColor, out int fps, out Tile tile, bool generateFallback, bool onlyIfDefined)
{
tile = null;
if (!GetTeamColorTileData(searchTilesets, name, shape, teamColor, out fps, out Tile[] tiles, generateFallback, onlyIfDefined))
bool success = GetTeamColorTileData(name, shape, teamColor, out fps, out Tile[] tiles, generateFallback, onlyIfDefined);
tile = tiles == null ? null : tiles[0];
return success;
}
public bool GetTeamColorTileData(string name, int shape, ITeamColor teamColor, out Tile tile, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(name, shape, teamColor, out int fps, out tile, generateFallback, onlyIfDefined);
}
public bool GetTeamColorTileData(string name, int shape, ITeamColor teamColor, out Tile tile)
{
return GetTeamColorTileData(name, shape, teamColor, out int fps, out tile, false, false);
}
public bool GetTileData(string name, int shape, out Tile tile, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(name, shape, null, out tile, generateFallback, onlyIfDefined);
}
public bool GetTileData(string name, int shape, out Tile tile)
{
return GetTeamColorTileData(name, shape, null, out tile, false, false);
}
public int GetTileDataLength(string name)
{
if (this.theater == null)
{
return false;
return 0;
}
tile = tiles[0];
return true;
}
public bool GetTeamColorTileData(IEnumerable<string> searchTilesets, string name, int shape, ITeamColor teamColor, out Tile[] tiles, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(searchTilesets, name, shape, teamColor, out int fps, out tiles, generateFallback, onlyIfDefined);
}
public bool GetTeamColorTileData(IEnumerable<string> searchTilesets, string name, int shape, ITeamColor teamColor, out Tile[] tiles)
{
return GetTeamColorTileData(searchTilesets, name, shape, teamColor, out int fps, out tiles, false, false);
}
public bool GetTeamColorTileData(IEnumerable<string> searchTilesets, string name, int shape, ITeamColor teamColor, out Tile tile, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(searchTilesets, name, shape, teamColor, out int fps, out tile, generateFallback, onlyIfDefined);
}
public bool GetTeamColorTileData(IEnumerable<string> searchTilesets, string name, int shape, ITeamColor teamColor, out Tile tile)
{
return GetTeamColorTileData(searchTilesets, name, shape, teamColor, out int fps, out tile, false, false);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out int fps, out Tile[] tiles, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out fps, out tiles, generateFallback, onlyIfDefined);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out int fps, out Tile[] tiles)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out fps, out tiles, false, false);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out int fps, out Tile tile, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out fps, out tile, generateFallback, onlyIfDefined);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out int fps, out Tile tile)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out fps, out tile, false, false);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out Tile[] tiles, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out tiles, generateFallback, onlyIfDefined);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out Tile[] tiles)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out tiles, false, false);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out Tile tile, bool generateFallback, bool onlyIfDefined)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out tile, generateFallback, onlyIfDefined);
}
public bool GetTileData(IEnumerable<string> searchTilesets, string name, int shape, out Tile tile)
{
return GetTeamColorTileData(searchTilesets, name, shape, null, out tile, false, false);
}
public int GetTileDataLength(IEnumerable<string> searchTilesets, string name)
{
foreach (var tileset in tilesets.Join(searchTilesets, x => x.Key, y => y, (x, y) => x.Value))
IEnumerable<string> searchTilesets = this.theater.Tilesets;
foreach (Tileset tileset in tilesets.Join(searchTilesets, x => x.Key, y => y, (x, y) => x.Value))
{
int frames = tileset.GetTileDataLength(name);
if (frames != -1)
@ -209,5 +188,27 @@ namespace MobiusEditor.Utility
}
return -1;
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
TextureManager.Dispose();
this.Reset(null);
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}