Added classic font rendering for techno triggers.

* Fixed overlap detection of walls in both TD and RA; they can only be obstructed by buildings now.
* Fixed bug where editing properties of an object would clear its trigger in the dropdown.
* Replaced usages of "6point.fnt" in RA by "editfnt.fnt", because it is constant, while "6point.fnt" differs in lores.mix and hires.mix.
This commit is contained in:
Nyerguds 2024-08-12 16:18:21 +02:00
parent 2ff36fbb7c
commit d14a46cb79
8 changed files with 108 additions and 75 deletions

View File

@ -151,7 +151,6 @@ namespace MobiusEditor.Controls
captureUnknownImage = ToolStripRenderer.CreateDisabledImage(captureImage); captureUnknownImage = ToolStripRenderer.CreateDisabledImage(captureImage);
houseComboBox.DataSource = plugin.Map.Houses.Select(t => new TypeItem<HouseType>(t.Type.Name, t.Type)).ToArray(); houseComboBox.DataSource = plugin.Map.Houses.Select(t => new TypeItem<HouseType>(t.Type.Name, t.Type)).ToArray();
missionComboBox.DataSource = plugin.Map.MissionTypes; missionComboBox.DataSource = plugin.Map.MissionTypes;
UpdateDataSource();
Disposed += (sender, e) => Disposed += (sender, e) =>
{ {
Object = null; Object = null;
@ -166,14 +165,22 @@ namespace MobiusEditor.Controls
private void UpdateDataSource() private void UpdateDataSource()
{ {
if (obj == null)
{
return;
}
string selected = triggerComboBox.SelectedItem as string; string selected = triggerComboBox.SelectedItem as string;
triggerComboBox.DataBindings.Clear(); triggerComboBox.DataBindings.Clear();
triggerComboBox.SelectedIndexChanged -= this.TriggerComboBox_SelectedIndexChanged; triggerComboBox.SelectedIndexChanged -= this.TriggerComboBox_SelectedIndexChanged;
triggerComboBox.DataSource = null; triggerComboBox.DataSource = null;
triggerComboBox.Items.Clear(); triggerComboBox.Items.Clear();
string[] items; string[] items;
Boolean isAircraft = obj is Unit un && un.Type.IsAircraft; bool isAircraft = obj is Unit un && un.Type.IsAircraft;
Boolean isOnMap = true; bool isOnMap = true;
if (selected == null && obj is ITechno tch)
{
selected = tch.Trigger;
}
switch (obj) switch (obj)
{ {
case Infantry infantry: case Infantry infantry:
@ -206,7 +213,6 @@ namespace MobiusEditor.Controls
int sel = triggerComboBox.SelectedIndex; int sel = triggerComboBox.SelectedIndex;
triggerComboBox.SelectedIndexChanged += this.TriggerComboBox_SelectedIndexChanged; triggerComboBox.SelectedIndexChanged += this.TriggerComboBox_SelectedIndexChanged;
triggerComboBox.SelectedItem = items[selectIndex]; triggerComboBox.SelectedItem = items[selectIndex];
TriggerComboBox_SelectedIndexChanged(triggerComboBox, new EventArgs());
if (sel == selectIndex) if (sel == selectIndex)
{ {
TriggerComboBox_SelectedIndexChanged(triggerComboBox, new EventArgs()); TriggerComboBox_SelectedIndexChanged(triggerComboBox, new EventArgs());

View File

@ -12,7 +12,8 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. namespace MobiusEditor // along with this program. If not, see <https://www.gnu.org/licenses/>.
namespace MobiusEditor
{ {
partial class GameInstallationPathForm partial class GameInstallationPathForm
{ {

View File

@ -260,16 +260,16 @@ namespace MobiusEditor.Model
public enum ClassicFont public enum ClassicFont
{ {
/// <summary>Font used for Waypoints</summary> /// <summary>Font used for Waypoints.</summary>
Waypoints, Waypoints,
/// <summary>Font used for waypoints with longer names. Separate because it needs a smaller font to fit inside one cell.</summary> /// <summary>Font used for waypoints with longer names. Separate because it needs a smaller font to fit inside one cell.</summary>
WaypointsLong, WaypointsLong,
/// <summary>Font used for cell triggers</summary> /// <summary>Font used for cell triggers.</summary>
CellTriggers, CellTriggers,
/// <summary>Font used for techno triggers, except infantry</summary> /// <summary>Font used for techno triggers on multi-cell objects.</summary>
TechnoTriggers, TechnoTriggers,
/// <summary>Font used for infantry techno triggers. Separate because it might need to be smaller.</summary> /// <summary>Font used for one-cell techno triggers. Separate because it might need to be smaller.</summary>
InfantryTriggers, TechnoTriggersSmall,
/// <summary>Font used for rebuild priority numbers on buildings.</summary> /// <summary>Font used for rebuild priority numbers on buildings.</summary>
RebuildPriority, RebuildPriority,
/// <summary>Font used for "FAKE" labels on buildings.</summary> /// <summary>Font used for "FAKE" labels on buildings.</summary>

View File

@ -252,7 +252,7 @@ namespace MobiusEditor.RedAlert
break; break;
case ClassicFont.WaypointsLong: case ClassicFont.WaypointsLong:
crop = true; crop = true;
fontName = "6point.fnt"; fontName = "editfnt.fnt";
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor, 2, 3); remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor, 2, 3);
break; break;
case ClassicFont.CellTriggers: case ClassicFont.CellTriggers:
@ -266,14 +266,18 @@ namespace MobiusEditor.RedAlert
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor); remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor);
break; break;
case ClassicFont.TechnoTriggers: case ClassicFont.TechnoTriggers:
case ClassicFont.InfantryTriggers: crop = true;
fontName = "editfnt.fnt";
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor, 2, 3);
break;
case ClassicFont.TechnoTriggersSmall:
crop = true; crop = true;
fontName = "3point.fnt"; fontName = "3point.fnt";
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor); remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor);
break; break;
case ClassicFont.FakeLabels: case ClassicFont.FakeLabels:
crop = true; crop = true;
fontName = "6point.fnt"; fontName = "editfnt.fnt";
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor, 2, 3); remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor, 2, 3);
break; break;
} }

View File

@ -2476,7 +2476,7 @@ namespace MobiusEditor.RedAlert
modified = true; modified = true;
continue; continue;
} }
if ((overlayType.IsWall || overlayType.IsSolid) && Map.Technos.ObjectAt(i, out ICellOccupier techno)) if ((overlayType.IsWall || overlayType.IsSolid) && Map.Buildings.ObjectAt(i, out ICellOccupier techno))
{ {
string desc = overlayType.IsWall ? "Wall" : "Solid overlay"; string desc = overlayType.IsWall ? "Wall" : "Solid overlay";
if (techno is Building building) if (techno is Building building)
@ -2484,21 +2484,6 @@ namespace MobiusEditor.RedAlert
errors.Add(string.Format("{0} '{1}' overlaps structure '{2}' at cell {3}; skipping.", desc, overlayType.Name, building.Type.Name, i)); errors.Add(string.Format("{0} '{1}' overlaps structure '{2}' at cell {3}; skipping.", desc, overlayType.Name, building.Type.Name, i));
modified = true; modified = true;
} }
else if (techno is Terrain terrain)
{
errors.Add(string.Format("{0} '{1}' overlaps terrain '{2}' at cell {3}; skipping.", desc, overlayType.Name, terrain.Type.Name, i));
modified = true;
}
else if (techno is Unit unit)
{
errors.Add(string.Format("{0} '{1}' overlaps unit '{2}' at cell {3}; skipping.", desc, overlayType.Name, unit.Type.Name, i));
modified = true;
}
else if (techno is InfantryGroup)
{
errors.Add(string.Format("{0} '{1}' overlaps infantry at cell {2}; skipping.", desc, overlayType.Name, i));
modified = true;
}
else else
{ {
errors.Add(string.Format("{0} '{1}' overlaps unknown techno in cell {2}; skipping.", desc, overlayType.Name, i)); errors.Add(string.Format("{0} '{1}' overlaps unknown techno in cell {2}; skipping.", desc, overlayType.Name, i));

View File

@ -2025,16 +2025,16 @@ namespace MobiusEditor.Render
public static void RenderAllTechnoTriggers(Graphics graphics, GameInfo gameInfo, OccupierSet<ICellOccupier> mapTechnos, OccupierSet<ICellOccupier> mapBuildings, Rectangle visibleCells, Size tileSize, MapLayerFlag layersToRender, Color color, string toPick, bool excludePick) public static void RenderAllTechnoTriggers(Graphics graphics, GameInfo gameInfo, OccupierSet<ICellOccupier> mapTechnos, OccupierSet<ICellOccupier> mapBuildings, Rectangle visibleCells, Size tileSize, MapLayerFlag layersToRender, Color color, string toPick, bool excludePick)
{ {
string classicFont = null; string classicFontLarge = null;
bool cropClassicFont = false; bool cropClassicFontLarge = false;
string classicFontInf = null; string classicFontSmall = null;
bool cropClassicFontInf = false; bool cropClassicFontSmall = false;
TeamRemap remapClassicFont = null; TeamRemap remapClassicFontLarge = null;
TeamRemap remapClassicFontInf = null; TeamRemap remapClassicFontSmall = null;
if (Globals.TheTilesetManager is TilesetManagerClassic tsmc && Globals.TheTeamColorManager is TeamRemapManager trm) if (Globals.TheTilesetManager is TilesetManagerClassic tsmc && Globals.TheTeamColorManager is TeamRemapManager trm)
{ {
classicFont = gameInfo.GetClassicFontInfo(ClassicFont.TechnoTriggers, tsmc, trm, color, out cropClassicFont, out remapClassicFont); classicFontLarge = gameInfo.GetClassicFontInfo(ClassicFont.TechnoTriggers, tsmc, trm, color, out cropClassicFontLarge, out remapClassicFontLarge);
classicFontInf = gameInfo.GetClassicFontInfo(ClassicFont.InfantryTriggers, tsmc, trm, color, out cropClassicFontInf, out remapClassicFontInf); classicFontSmall = gameInfo.GetClassicFontInfo(ClassicFont.TechnoTriggersSmall, tsmc, trm, color, out cropClassicFontSmall, out remapClassicFontSmall);
} }
double tileScaleHor = tileSize.Width / 128.0; double tileScaleHor = tileSize.Width / 128.0;
float borderSize = Math.Max(0.5f, tileSize.Width / 60.0f); float borderSize = Math.Max(0.5f, tileSize.Width / 60.0f);
@ -2051,8 +2051,9 @@ namespace MobiusEditor.Render
{ {
if (visibleCells.IntersectsWith(new Rectangle(topLeft, terrain.Type.Size))) if (visibleCells.IntersectsWith(new Rectangle(topLeft, terrain.Type.Size)))
{ {
Size size = new Size(terrain.Type.Size.Width * tileSize.Width, terrain.Type.Size.Height * tileSize.Height); Size size = new Size(terrain.Type.Size.Width * Globals.OriginalTileWidth, terrain.Type.Size.Height * Globals.OriginalTileHeight);
triggers = new (string, Rectangle, int)[] { (terrain.Trigger, new Rectangle(location, size), terrain.IsPreview ? Globals.PreviewAlphaInt : 255) }; triggers = new (string, Rectangle, int)[] { (terrain.Trigger, new Rectangle(location, size),
terrain.IsPreview ? Globals.PreviewAlphaInt : 256) };
} }
} }
} }
@ -2062,7 +2063,8 @@ namespace MobiusEditor.Render
{ {
if (visibleCells.Contains(topLeft)) if (visibleCells.Contains(topLeft))
{ {
triggers = new (string, Rectangle, int)[] { (unit.Trigger, new Rectangle(location, tileSize), unit.IsPreview ? Globals.PreviewAlphaInt : 255) }; triggers = new (string, Rectangle, int)[] { (unit.Trigger, new Rectangle(location, Globals.OriginalTileSize),
unit.IsPreview ? Globals.PreviewAlphaInt : 256) };
} }
} }
} }
@ -2082,7 +2084,7 @@ namespace MobiusEditor.Render
{ {
continue; continue;
} }
Size size = tileSize; Size size = Globals.OriginalTileSize;
Size offset = Size.Empty; Size offset = Size.Empty;
switch ((InfantryStoppingType)i) switch ((InfantryStoppingType)i)
{ {
@ -2104,7 +2106,7 @@ namespace MobiusEditor.Render
break; break;
} }
Rectangle bounds = new Rectangle(location + offset, size); Rectangle bounds = new Rectangle(location + offset, size);
infantryTriggers.Add((infantry.Trigger, bounds, infantry.IsPreview ? Globals.PreviewAlphaInt : 255)); infantryTriggers.Add((infantry.Trigger, bounds, infantry.IsPreview ? Globals.PreviewAlphaInt : 256));
} }
triggers = infantryTriggers.ToArray(); triggers = infantryTriggers.ToArray();
} }
@ -2126,8 +2128,9 @@ namespace MobiusEditor.Render
{ {
if (visibleCells.IntersectsWith(new Rectangle(topLeft, building.Type.Size))) if (visibleCells.IntersectsWith(new Rectangle(topLeft, building.Type.Size)))
{ {
Size size = new Size(building.Type.Size.Width * tileSize.Width, building.Type.Size.Height * tileSize.Height); Size size = new Size(building.Type.Size.Width * Globals.OriginalTileWidth, building.Type.Size.Height * Globals.OriginalTileHeight);
allTriggers.Add((building.Trigger, new Rectangle(location, size), building.IsPreview ? Globals.PreviewAlphaInt : 255)); allTriggers.Add((building.Trigger, new Rectangle(location, size),
building.IsPreview ? Globals.PreviewAlphaInt : 256));
} }
} }
} }
@ -2142,19 +2145,56 @@ namespace MobiusEditor.Render
|| (excludePick && !x.trigger.Equals(toPick, StringComparison.OrdinalIgnoreCase)) || (excludePick && !x.trigger.Equals(toPick, StringComparison.OrdinalIgnoreCase))
|| (!excludePick && x.trigger.Equals(toPick, StringComparison.OrdinalIgnoreCase)))) || (!excludePick && x.trigger.Equals(toPick, StringComparison.OrdinalIgnoreCase))))
{ {
Color alphaColor = Color.FromArgb(alpha, color); // Larger than a single cell.
using (SolidBrush technoTriggerBackgroundBrush = new SolidBrush(Color.FromArgb(96 * alpha / 256, Color.Black))) bool isLarge = bounds.Width > Globals.OriginalTileWidth;
string classicFont = isLarge ? classicFontLarge : classicFontSmall;
bool cropClassicFont = isLarge ? cropClassicFontLarge : cropClassicFontSmall;
TeamRemap remapClassicFont = isLarge ? remapClassicFontLarge : remapClassicFontSmall;
Color alphaColor = Color.FromArgb(alpha.Restrict(0,255), color);
if (classicFont == null)
{
int width = bounds.Width * tileSize.Width / Globals.OriginalTileWidth;
int height = bounds.Height * tileSize.Height / Globals.OriginalTileHeight;
Rectangle realBounds = new Rectangle(bounds.Location, new Size(width, height));
using (SolidBrush technoTriggerBackgroundBrush = new SolidBrush(Color.FromArgb((96 * alpha / 256).Restrict(0, 255), Color.Black)))
using (SolidBrush technoTriggerBrush = new SolidBrush(alphaColor)) using (SolidBrush technoTriggerBrush = new SolidBrush(alphaColor))
using (Pen technoTriggerPen = new Pen(alphaColor, borderSize)) using (Pen technoTriggerPen = new Pen(alphaColor, borderSize))
using (Font font = graphics.GetAdjustedFont(trigger, SystemFonts.DefaultFont, bounds.Width, bounds.Height, using (Font font = graphics.GetAdjustedFont(trigger, SystemFonts.DefaultFont, width, height,
Math.Max(1, (int)Math.Round(12 * tileScaleHor)), Math.Max(1, (int)Math.Round(24 * tileScaleHor)), stringFormat, true)) Math.Max(1, (int)Math.Round(12 * tileScaleHor)), Math.Max(1, (int)Math.Round(24 * tileScaleHor)), stringFormat, true))
{ {
SizeF textBounds = graphics.MeasureString(trigger, font, bounds.Width, stringFormat); SizeF textBounds = graphics.MeasureString(trigger, font, width, stringFormat);
RectangleF backgroundBounds = new RectangleF(bounds.Location, textBounds); RectangleF backgroundBounds = new RectangleF(bounds.Location, textBounds);
backgroundBounds.Offset((bounds.Width - textBounds.Width) / 2.0f, (bounds.Height - textBounds.Height) / 2.0f); backgroundBounds.Offset((width - textBounds.Width) / 2.0f, (height - textBounds.Height) / 2.0f);
graphics.FillRectangle(technoTriggerBackgroundBrush, backgroundBounds); graphics.FillRectangle(technoTriggerBackgroundBrush, backgroundBounds);
graphics.DrawRectangle(technoTriggerPen, Rectangle.Round(backgroundBounds)); graphics.DrawRectangle(technoTriggerPen, Rectangle.Round(backgroundBounds));
graphics.DrawString(trigger, font, technoTriggerBrush, bounds, stringFormat); graphics.DrawString(trigger, font, technoTriggerBrush, realBounds, stringFormat);
}
}
else
{
int[] indices = Encoding.ASCII.GetBytes(trigger).Select(x => (int)x).ToArray();
using (SolidBrush technoTriggerBackgroundBrush = new SolidBrush(Color.FromArgb(96, Color.Black)))
using (Pen technoTriggerPen = new Pen(color, 1))
using (Bitmap txt = RenderTextFromSprite(classicFont, remapClassicFont, Size.Empty, indices, false, cropClassicFont))
using (Bitmap txt2 = new Bitmap(txt.Width + 4, txt.Height + 4))
using (ImageAttributes imageAttributes = new ImageAttributes())
{
txt2.SetResolution(96, 96);
using (Graphics txt2g = Graphics.FromImage(txt2))
{
txt2g.FillRectangle(technoTriggerBackgroundBrush, new Rectangle(1, 1, txt2.Width - 2, txt2.Height - 2));
txt2g.DrawRectangle(technoTriggerPen, new Rectangle(0, 0, txt2.Width - 1, txt2.Height - 1));
txt2g.DrawImage(txt, new Rectangle(2, 2, txt.Width, txt.Height));
}
imageAttributes.SetColorMatrix(GetColorMatrix(Color.White, 1.0f, alpha / 256.0f));
int paintOffsX = (bounds.Width - txt2.Width) / 2 * tileSize.Width / Globals.OriginalTileWidth;
int paintOffsY = (bounds.Height - txt2.Height) / 2 * tileSize.Width / Globals.OriginalTileWidth;
int textWidth = txt2.Width * tileSize.Width / Globals.OriginalTileWidth;
int textHeight = txt2.Height * tileSize.Width / Globals.OriginalTileWidth;
Rectangle paintBounds = new Rectangle(bounds.Location, new Size(textWidth, textHeight));
paintBounds.Offset(new Point(paintOffsX, paintOffsY));
graphics.DrawImage(txt2, paintBounds, 0, 0, txt2.Width, txt2.Height, GraphicsUnit.Pixel, imageAttributes);
}
} }
} }
} }
@ -2244,7 +2284,6 @@ namespace MobiusEditor.Render
Globals.TheShapeCacheManager.AddImage(wpId, wpBm); Globals.TheShapeCacheManager.AddImage(wpId, wpBm);
Rectangle paintRect = new Rectangle(paintBounds.Location.X, paintBounds.Location.Y, wpBm.Width, wpBm.Height); Rectangle paintRect = new Rectangle(paintBounds.Location.X, paintBounds.Location.Y, wpBm.Width, wpBm.Height);
graphics.DrawImage(wpBm, paintRect, 0, 0, wpBm.Width, wpBm.Height, GraphicsUnit.Pixel, imageAttributes); graphics.DrawImage(wpBm, paintRect, 0, 0, wpBm.Width, wpBm.Height, GraphicsUnit.Pixel, imageAttributes);
} }
} }
} }
@ -2883,12 +2922,13 @@ namespace MobiusEditor.Render
} }
else else
{ {
// Solid overlays in the buildings list.
isBuilding = building != null; isBuilding = building != null;
} }
bool isTechno = techno != null || isBuilding; bool isTechno = techno != null || isBuilding;
// Skip if it's the techno-loop and there's no techno, // Skip if it's a techno-loop and there's no techno,
// or if it's not the techno-loop and there is a techno (to avoid overlap). // or if it's not a techno-loop and there is a techno (to avoid overlap).
if ((isTechno && !forTechnos) || (!isTechno && forTechnos)) if ((forTechnos && !isTechno) || (!forTechnos && isTechno))
{ {
continue; continue;
} }
@ -3165,9 +3205,12 @@ namespace MobiusEditor.Render
int curWidth = 0; int curWidth = 0;
if (lineLength == 0 || maxHeight - minTop == 0) if (lineLength == 0 || maxHeight - minTop == 0)
{ {
return new Bitmap(2, 2); Bitmap bm = new Bitmap(2, 2);
bm.SetResolution(96, 96);
return bm;
} }
Bitmap bitmap = new Bitmap(lineLength, maxHeight - minTop, PixelFormat.Format32bppArgb); Bitmap bitmap = new Bitmap(lineLength, maxHeight - minTop, PixelFormat.Format32bppArgb);
bitmap.SetResolution(96, 96);
using (Graphics g = Graphics.FromImage(bitmap)) using (Graphics g = Graphics.FromImage(bitmap))
{ {
for (int i = 0; i < nrOfChars; ++i) for (int i = 0; i < nrOfChars; ++i)

View File

@ -179,7 +179,11 @@ namespace MobiusEditor.TiberianDawn
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor); remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor);
break; break;
case ClassicFont.TechnoTriggers: case ClassicFont.TechnoTriggers:
case ClassicFont.InfantryTriggers: crop = true;
fontName = "scorefnt.fnt";
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor);
break;
case ClassicFont.TechnoTriggersSmall:
crop = true; crop = true;
fontName = "3point.fnt"; fontName = "3point.fnt";
remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor); remap = GetClassicFontRemapSimple(fontName, tsmc, trm, textColor);

View File

@ -2117,7 +2117,7 @@ namespace MobiusEditor.TiberianDawn
modified = true; modified = true;
continue; continue;
} }
if ((overlayType.IsWall || overlayType.IsSolid) && Map.Technos.ObjectAt(cell, out ICellOccupier techno)) if ((overlayType.IsWall || overlayType.IsSolid) && Map.Buildings.ObjectAt(cell, out ICellOccupier techno))
{ {
string desc = overlayType.IsWall ? "Wall" : "Solid overlay"; string desc = overlayType.IsWall ? "Wall" : "Solid overlay";
if (techno is Building building) if (techno is Building building)
@ -2125,19 +2125,9 @@ namespace MobiusEditor.TiberianDawn
errors.Add(string.Format("{0} '{1}' overlaps structure '{2}' at cell {3}; skipping.", desc, overlayType.Name, building.Type.Name, cell)); errors.Add(string.Format("{0} '{1}' overlaps structure '{2}' at cell {3}; skipping.", desc, overlayType.Name, building.Type.Name, cell));
modified = true; modified = true;
} }
else if (techno is Terrain terrain) else if (techno is Overlay ovl)
{ {
errors.Add(string.Format("{0} '{1}' overlaps terrain '{2}' at cell {3}; skipping.", desc, overlayType.Name, terrain.Type.Name, cell)); errors.Add(string.Format("{0} '{1}' overlaps overlay '{2}' at cell {3}; skipping.", desc, overlayType.Name, ovl.Type.Name, cell));
modified = true;
}
else if (techno is Unit unit)
{
errors.Add(string.Format("{0} '{1}' overlaps unit '{2}' at cell {3}; skipping.", desc, overlayType.Name, unit.Type.Name, cell));
modified = true;
}
else if (techno is InfantryGroup)
{
errors.Add(string.Format("{0} '{1}' overlaps infantry at cell {2}; skipping.", desc, overlayType.Name, cell));
modified = true; modified = true;
} }
else else