Cleaner loading of dummy data.

This commit is contained in:
Nyeguds 2022-08-01 18:20:48 +02:00
parent b830a1df52
commit 2000c8c99d
6 changed files with 166 additions and 59 deletions

View File

@ -47,6 +47,10 @@ namespace MobiusEditor.Model
public Image Thumbnail { get; set; }
public String GraphicsSource { get; private set; }
public int ForceTileNr { get; private set; }
public bool[,] OccupyMask => new bool[1, 1] { { true } };
public bool IsResource => (Flag & (OverlayTypeFlag.TiberiumOrGold | OverlayTypeFlag.Gems)) != OverlayTypeFlag.None;
@ -64,15 +68,22 @@ namespace MobiusEditor.Model
// No reason not to allow placing decorations and flag pedestal.
public bool IsPlaceable => (Flag & (OverlayTypeFlag.Crate | OverlayTypeFlag.Decoration | OverlayTypeFlag.Flag)) != OverlayTypeFlag.None;
public OverlayType(sbyte id, string name, string textId, TheaterType[] theaters, OverlayTypeFlag flag)
public OverlayType(sbyte id, string name, string textId, TheaterType[] theaters, OverlayTypeFlag flag, String graphicsLoadOverride, int forceTileNr)
{
ID = id;
Name = name;
DisplayName = Globals.TheGameTextManager[textId] + " (" + Name.ToUpperInvariant() + ")";
GraphicsSource = graphicsLoadOverride == null ? name : graphicsLoadOverride;
ForceTileNr = forceTileNr;
DisplayName = Globals.TheGameTextManager[textId] + " (" + GraphicsSource.ToUpperInvariant() + ")";
Theaters = theaters;
Flag = flag;
}
public OverlayType(sbyte id, string name, string textId, TheaterType[] theaters, OverlayTypeFlag flag)
:this(id, name, textId, theaters, flag, null, -1)
{
}
public OverlayType(sbyte id, string name, string textId, OverlayTypeFlag flag)
: this(id, name, textId, null, flag)
{
@ -124,7 +135,8 @@ namespace MobiusEditor.Model
public void Init(TheaterType theater)
{
var oldImage = Thumbnail;
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 0, out Tile tile, (Flag & OverlayTypeFlag.Decoration) != 0))
int tilenr = ForceTileNr == -1 ? 0 : ForceTileNr;
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, GraphicsSource, tilenr, out Tile tile, (Flag & OverlayTypeFlag.Decoration) != 0))
{
var size = tile.Image.Size;
var maxSize = new Size(Globals.OriginalTileWidth, Globals.OriginalTileWidth);

View File

@ -47,7 +47,12 @@ namespace MobiusEditor.Model
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public object Clone()
public object ICloneable.Clone()
{
return Clone();
}
public Smudge Clone()
{
return new Smudge()
{

View File

@ -109,9 +109,7 @@ namespace MobiusEditor.Render
var tileSize = new Size(Globals.OriginalTileWidth / tileScale, Globals.OriginalTileHeight / tileScale);
var tiberiumOrGoldTypes = map.OverlayTypes.Where(t => t.IsTiberiumOrGold).Select(t => t).ToArray();
var gemTypes = map.OverlayTypes.Where(t => t.IsGem).ToArray();
var overlappingRenderList = new List<(Rectangle, Action<Graphics>)>();
Func<IEnumerable<Point>> renderLocations = null;
if (locations != null)
{
@ -131,18 +129,15 @@ namespace MobiusEditor.Render
}
renderLocations = allCells;
}
if ((layers & MapLayerFlag.Template) != MapLayerFlag.None)
{
foreach (var topLeft in renderLocations())
{
map.Metrics.GetCell(topLeft, out int cell);
var template = map.Templates[topLeft];
TemplateType ttype = template?.Type ?? map.TemplateTypes.Where(t => t.Flag == TemplateTypeFlag.Clear).FirstOrDefault();
var name = ttype.Name;
var icon = template?.Icon ?? ((cell & 0x03) | ((cell >> 4) & 0x0C));
if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, name, icon, out Tile tile))
{
var renderBounds = new Rectangle(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height, tileSize.Width, tileSize.Height);
@ -176,7 +171,6 @@ namespace MobiusEditor.Render
{
continue;
}
if ((overlay.Type.IsResource && ((layers & MapLayerFlag.Resources) != MapLayerFlag.None)) ||
(overlay.Type.IsWall && ((layers & MapLayerFlag.Walls) != MapLayerFlag.None)) ||
((layers & MapLayerFlag.Overlay) != MapLayerFlag.None))
@ -194,13 +188,11 @@ namespace MobiusEditor.Render
{
continue;
}
string tileName = terrain.Type.Name;
if ((terrain.Type.TemplateType & TemplateTypeFlag.OreMine) != TemplateTypeFlag.None)
{
tileName = "OREMINE";
}
if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, tileName, terrain.Icon, out Tile tile))
{
var tint = terrain.Tint;
@ -218,7 +210,6 @@ namespace MobiusEditor.Render
);
imageAttributes.SetColorMatrix(colorMatrix);
}
var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
var size = new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale);
var maxSize = new Size(terrain.Type.Size.Width * tileSize.Width, terrain.Type.Size.Height * tileSize.Height);
@ -246,7 +237,6 @@ namespace MobiusEditor.Render
}
}
}
if ((layers & MapLayerFlag.Buildings) != MapLayerFlag.None)
{
foreach (var (topLeft, building) in map.Buildings.OfType<Building>())
@ -255,11 +245,9 @@ namespace MobiusEditor.Render
{
continue;
}
overlappingRenderList.Add(Render(gameType, map.Theater, topLeft, tileSize, tileScale, building));
}
}
if ((layers & MapLayerFlag.Infantry) != MapLayerFlag.None)
{
foreach (var (topLeft, infantryGroup) in map.Technos.OfType<InfantryGroup>())
@ -268,7 +256,6 @@ namespace MobiusEditor.Render
{
continue;
}
for (int i = 0; i < infantryGroup.Infantry.Length; ++i)
{
var infantry = infantryGroup.Infantry[i];
@ -276,12 +263,10 @@ namespace MobiusEditor.Render
{
continue;
}
overlappingRenderList.Add(Render(map.Theater, topLeft, tileSize, infantry, (InfantryStoppingType)i));
}
}
}
if ((layers & MapLayerFlag.Units) != MapLayerFlag.None)
{
foreach (var (topLeft, unit) in map.Technos.OfType<Unit>())
@ -290,11 +275,9 @@ namespace MobiusEditor.Render
{
continue;
}
overlappingRenderList.Add(Render(gameType, map.Theater, topLeft, tileSize, unit));
}
}
foreach (var (location, renderer) in overlappingRenderList.Where(x => !x.Item1.IsEmpty).OrderBy(x => x.Item1.Bottom))
{
renderer(graphics);
@ -323,17 +306,14 @@ namespace MobiusEditor.Render
);
imageAttributes.SetColorMatrix(colorMatrix);
}
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, smudge.Type.Name, smudge.Icon, out Tile tile))
{
var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
var smudgeBounds = new Rectangle(location, smudge.Type.RenderSize);
void render(Graphics g)
{
g.DrawImage(tile.Image, smudgeBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
}
return (smudgeBounds, render);
}
else
@ -345,7 +325,7 @@ namespace MobiusEditor.Render
public static (Rectangle, Action<Graphics>) Render(TheaterType theater, OverlayType[] tiberiumOrGoldTypes, OverlayType[] gemTypes, Point topLeft, Size tileSize, int tileScale, Overlay overlay)
{
var name = overlay.Type.Name;
string name;
if (overlay.Type.IsGem)
{
name = gemTypes[new Random(randomSeed ^ topLeft.GetHashCode()).Next(tiberiumOrGoldTypes.Length)].Name;
@ -354,15 +334,19 @@ namespace MobiusEditor.Render
{
name = tiberiumOrGoldTypes[new Random(randomSeed ^ topLeft.GetHashCode()).Next(tiberiumOrGoldTypes.Length)].Name;
}
else
{
name = overlay.Type.GraphicsSource;
}
// For Decoration types, generate dummy if not found.
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, name, overlay.Icon, out Tile tile, (overlay.Type.Flag & OverlayTypeFlag.Decoration) != 0))
int icon = overlay.Type.ForceTileNr == -1 ? overlay.Icon : overlay.Type.ForceTileNr;
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, name, icon, out Tile tile, (overlay.Type.Flag & OverlayTypeFlag.Decoration) != 0))
{
var size = (overlay.Type.IsCrate || overlay.Type.IsFlag) ? new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale) : tileSize;
var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height)
+ new Size(tileSize.Width / 2, tileSize.Height / 2)
- new Size(size.Width / 2, size.Height / 2);
var overlayBounds = new Rectangle(location, size);
var tint = overlay.Tint;
void render(Graphics g)
{
@ -380,15 +364,13 @@ namespace MobiusEditor.Render
);
imageAttributes.SetColorMatrix(colorMatrix);
}
g.DrawImage(tile.Image, overlayBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
}
return (overlayBounds, render);
}
else
{
Debug.Print(string.Format("Overlay {0} ({1}) not found", overlay.Type.Name, overlay.Icon));
Debug.Print(string.Format("Overlay {0} ({1}) not found", name, icon));
return (Rectangle.Empty, (g) => { });
}
}
@ -396,7 +378,6 @@ namespace MobiusEditor.Render
public static (Rectangle, Action<Graphics>) Render(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, int tileScale, Building building)
{
var tint = building.Tint;
var stringFormat = new StringFormat
{
Alignment = StringAlignment.Center,
@ -434,7 +415,6 @@ namespace MobiusEditor.Render
icon = damageIcon;
}
}
if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.Tilename, icon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out Tile tile))
{
var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
@ -443,7 +423,6 @@ namespace MobiusEditor.Render
// Graphics are too large. Scale them down using the largest dimension.
if ((size.Width >= size.Height) && (size.Width > maxSize.Width))
{
size.Height = size.Height * maxSize.Width / size.Width;
size.Width = maxSize.Width;
}
@ -457,7 +436,6 @@ namespace MobiusEditor.Render
int locY = (maxSize.Height - size.Height) / 2 + location.Y;
var paintBounds = new Rectangle(locX, locY, size.Width, size.Height);
var buildingBounds = new Rectangle(location, maxSize);
Tile factoryOverlayTile = null;
// Draw no factory overlay over the collapse frame.
if (building.Type.FactoryOverlay != null && (building.Strength > 1 || !hasCollapseFrame))
@ -470,7 +448,6 @@ namespace MobiusEditor.Render
}
Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.FactoryOverlay, overlayIcon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out factoryOverlayTile);
}
void render(Graphics g)
{
var imageAttributes = new ImageAttributes();
@ -504,7 +481,6 @@ namespace MobiusEditor.Render
g.DrawString(text, SystemFonts.CaptionFont, fakeTextBrush, textBounds, stringFormat);
}
}
if (building.BasePriority >= 0)
{
var text = building.BasePriority.ToString();
@ -521,26 +497,23 @@ namespace MobiusEditor.Render
}
}
}
return (buildingBounds, render);
}
else
{
Debug.Print(string.Format("Building {0} (0) not found", building.Type.Name));
return (Rectangle.Empty, (g) => { });
}
}
}
public static (Rectangle, Action<Graphics>) Render(TheaterType theater, 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))
{
var baseLocation = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height)
+ new Size(tileSize.Width / 2, tileSize.Height / 2);
var offset = Point.Empty;
switch (infantryStoppingType)
{
@ -562,7 +535,6 @@ namespace MobiusEditor.Render
break;
}
baseLocation.Offset(offset);
var virtualBounds = new Rectangle(
new Point(baseLocation.X - (tile.OpaqueBounds.Width / 2), baseLocation.Y - tile.OpaqueBounds.Height),
tile.OpaqueBounds.Size
@ -571,7 +543,6 @@ namespace MobiusEditor.Render
baseLocation - new Size(infantry.Type.RenderSize.Width / 2, infantry.Type.RenderSize.Height / 2),
infantry.Type.RenderSize
);
var tint = infantry.Tint;
void render(Graphics g)
{
@ -591,7 +562,6 @@ namespace MobiusEditor.Render
}
g.DrawImage(tile.Image, renderBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
}
return (virtualBounds, render);
}
else
@ -692,7 +662,6 @@ namespace MobiusEditor.Render
location - new Size(unit.Type.RenderSize.Width / 2, unit.Type.RenderSize.Height / 2),
unit.Type.RenderSize
);
Tile radarTile = null;
if ((unit.Type == RedAlert.UnitTypes.MGG) ||
(unit.Type == RedAlert.UnitTypes.MRJammer) ||
@ -700,7 +669,6 @@ namespace MobiusEditor.Render
{
Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, unit.Type.Name, 32, Globals.TheTeamColorManager[teamColor], out radarTile);
}
Tile turretTile = null;
if (unit.Type.HasTurret)
{
@ -777,7 +745,7 @@ namespace MobiusEditor.Render
{
if (unit.Type.IsVessel)
{
// TODO
}
else if (unit.Type == RedAlert.UnitTypes.Jeep)
{
@ -798,17 +766,14 @@ namespace MobiusEditor.Render
turretAdjust = TurretAdjust[Facing32[unit.Direction.ID]];
}
}
var turretBounds = renderBounds;
turretBounds.Offset(
turretAdjust.X * tileSize.Width / Globals.PixelWidth,
turretAdjust.Y * tileSize.Height / Globals.PixelHeight
);
g.DrawImage(turretTile.Image, turretBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
}
}
return (renderBounds, render);
}
else

View File

@ -188,7 +188,8 @@ namespace MobiusEditor.TiberianDawn
using (var iniReader = new StreamReader(iniPath))
using (var binReader = new BinaryReader(new FileStream(binPath, FileMode.Open, FileAccess.Read)))
{
ini.Parse(iniReader);
string iniText = FixRoad2Load(iniReader);
ini.Parse(iniText);
errors.AddRange(LoadINI(ini));
LoadBinary(binReader);
}
@ -221,6 +222,78 @@ namespace MobiusEditor.TiberianDawn
return errors;
}
private string FixRoad2Load(StreamReader iniReader)
{
string iniText = iniReader.ReadToEnd();
string[] iniTextArr = iniText.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
Dictionary<int, int> dupeRoadDetect = new Dictionary<int, int>();
Regex roadRegex = new Regex("^\\s*(\\d+)\\s*=\\s*" + OverlayTypes.Road.Name + "\\s*$", RegexOptions.IgnoreCase);
string road2dummy = "=" + OverlayTypes.Road2.Name.ToUpper();
bool inOverlay = false;
for (int i = 0; i < iniTextArr.Length; ++i)
{
string currLine = iniTextArr[i].Trim();
if (currLine.StartsWith("["))
{
inOverlay = "[Overlay]".Equals(currLine, StringComparison.InvariantCultureIgnoreCase);
continue;
}
if (inOverlay)
{
Match match = roadRegex.Match(currLine);
if (match.Success)
{
int cellNumber = Int32.Parse(match.Groups[1].Value);
int cur = dupeRoadDetect.TryGetValue(cellNumber, out int curVal) ? curVal : 0;
dupeRoadDetect[cellNumber] = cur + 1;
}
}
}
if (dupeRoadDetect.Any(k => k.Value > 1))
{
inOverlay = false;
List<string> newIniText = new List<string>();
for (int i = 0; i < iniTextArr.Length; ++i)
{
string currLine = iniTextArr[i].Trim();
if (currLine.StartsWith("["))
{
inOverlay = "[Overlay]".Equals(currLine, StringComparison.InvariantCultureIgnoreCase);
}
Match match;
if (!inOverlay || !(match = roadRegex.Match(iniTextArr[i])).Success)
{
newIniText.Add(currLine);
}
else
{
int cellNumber = Int32.Parse(match.Groups[1].Value);
int roadAmount;
if (dupeRoadDetect.TryGetValue(cellNumber, out roadAmount))
{
if (roadAmount == 1)
{
newIniText.Add(currLine);
}
else if (roadAmount > 1)
{
newIniText.Add(cellNumber + road2dummy);
// Ensures TryGetValue succeeds, but nothing is written for any following matches.
dupeRoadDetect[cellNumber] = -1;
}
}
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < newIniText.Count; ++i)
{
sb.Append(newIniText[i]).Append("\r\n");
}
iniText = sb.ToString();
}
return iniText;
}
private IEnumerable<string> LoadINI(INI ini)
{
var errors = new List<string>();
@ -878,7 +951,7 @@ namespace MobiusEditor.TiberianDawn
Dictionary<string, string> correctedEdges = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var edge in Globals.Edges)
correctedEdges.Add(edge, edge);
String defaultEdge = Globals.Edges.FirstOrDefault() ?? String.Empty;
string defaultEdge = Globals.Edges.FirstOrDefault() ?? string.Empty;
foreach (var house in Map.Houses)
{
if (house.Type.ID < 0)
@ -956,7 +1029,8 @@ namespace MobiusEditor.TiberianDawn
SaveINI(ini, fileType);
using (var iniWriter = new StreamWriter(iniPath))
{
iniWriter.Write(ini.ToString());
//iniWriter.Write(ini.ToString());
FixRoad2Save(ini, iniWriter);
}
using (var binStream = new FileStream(binPath, FileMode.Create))
@ -1026,6 +1100,36 @@ namespace MobiusEditor.TiberianDawn
return true;
}
private void FixRoad2Save(INI ini, StreamWriter iniWriter)
{
// Special code to make the second state of ROAD cells work.
string roadLine = "=" + OverlayTypes.Road.Name.ToUpperInvariant() + "\r\n";
Regex roadDetect = new Regex("^\\s*(\\d+)\\s*=\\s*" + OverlayTypes.Road2.Name + "\\s*$", RegexOptions.IgnoreCase);
string[] iniString = ini.ToString().Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
bool inOverlay = false;
for (int i = 0; i < iniString.Length; i++)
{
string currLine = iniString[i].Trim();
if (currLine.StartsWith("["))
{
inOverlay = "[Overlay]".Equals(currLine, StringComparison.InvariantCultureIgnoreCase);
}
Match match;
if (inOverlay && (match = roadDetect.Match(currLine)).Success)
{
string newRoad = match.Groups[1].Value + roadLine;
// Write twice to achieve second state.
iniWriter.Write(newRoad);
iniWriter.Write(newRoad);
}
else
{
iniWriter.Write(currLine);
iniWriter.Write("\r\n");
}
}
}
private void SaveINI(INI ini, FileType fileType)
{
if (extraSections != null)
@ -1245,7 +1349,7 @@ namespace MobiusEditor.TiberianDawn
Random rd = new Random();
foreach (var (cell, overlay) in Map.Overlay)
{
String overlayName = overlay.Type.Name;
string overlayName = overlay.Type.Name;
if (tiberium.IsMatch(overlayName))
overlayName = "TI" + rd.Next(1, 13);
overlaySection[cell.ToString()] = overlayName.ToUpperInvariant();

View File

@ -41,6 +41,7 @@ namespace MobiusEditor.TiberianDawn
public static readonly OverlayType Tiberium11 = new OverlayType(16, "ti11", OverlayTypeFlag.TiberiumOrGold);
public static readonly OverlayType Tiberium12 = new OverlayType(17, "ti12", OverlayTypeFlag.TiberiumOrGold);
public static readonly OverlayType Road = new OverlayType(18, "road", "Concrete Road", OverlayTypeFlag.Decoration);
public static readonly OverlayType Road2 = new OverlayType(19, "road2", "Concrete Road (full)", null, OverlayTypeFlag.Decoration, "road", 1);
// Not available to place down sadly.
//public static readonly OverlayType Squishy = new OverlayType(19, "SQUISH", OverlayTypeFlag.Decoration);
public static readonly OverlayType V12 = new OverlayType(20, "v12", "TEXT_STRUCTURE_TITLE_CIV12", new TheaterType[] { TheaterTypes.Temperate });

View File

@ -41,6 +41,8 @@ namespace MobiusEditor.Utility
public class Tileset
{
private static string DummyFormatTga = "DATA\\ART\\TEXTURES\\SRGB\\FALLBACK_DUMMY\\{0}_{1:D4}.tga";
private class TileData
{
public int FPS { get; set; }
@ -119,27 +121,45 @@ namespace MobiusEditor.Utility
{
return false;
}
if (!this.tiles.TryGetValue(name, out Dictionary<int, TileData> shapes) && !generateFallback)
Dictionary<int, TileData> shapes;
if (!this.tiles.TryGetValue(name, out shapes) && !generateFallback)
{
return false;
}
name = name.ToUpperInvariant();
if (shapes == null && generateFallback)
{
if (shape < 0)
{
shape = 0;
}
string dummy = String.Format(DummyFormatTga, name, shape);
shapes = new Dictionary<int, TileData>();
TileData dummyData = new TileData();
dummyData.Frames = new string[] { "DATA\\ART\\TEXTURES\\SRGB\\FALLBACK_DUMMY\\" + name + "_" + shape.ToString("D4") + ".tga" };
shapes.Add(0, dummyData);
dummyData.Frames = new string[] { dummy };
shapes.Add(shape, dummyData);
// Add it, so it's present for the next lookup.
this.tiles[name] = shapes;
}
if (shape < 0)
{
shape = Math.Max(0, shapes.Max(kv => kv.Key) + shape + 1);
}
if (!shapes.TryGetValue(shape, out TileData tileData))
{
return false;
if (generateFallback)
{
// Shape was found, but specific frame was not. Add it.
string dummy = String.Format(DummyFormatTga, name, shape);
tileData = new TileData();
tileData.Frames = new string[] { dummy };
shapes.Add(shape, tileData);
}
else
{
return false;
}
}
var key = teamColor?.Name ?? string.Empty;
if (!tileData.TeamColorTiles.TryGetValue(key, out Tile[] tileDataTiles))
{