diff --git a/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj b/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj
index a1efe9d..b6dcaa3 100644
--- a/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj
+++ b/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj
@@ -583,6 +583,7 @@
WaypointsToolDialog.cs
+
@@ -596,6 +597,7 @@
+
diff --git a/CnCTDRAMapEditor/Interface/ITeamColorManager.cs b/CnCTDRAMapEditor/Interface/ITeamColorManager.cs
index 701f304..c07ac51 100644
--- a/CnCTDRAMapEditor/Interface/ITeamColorManager.cs
+++ b/CnCTDRAMapEditor/Interface/ITeamColorManager.cs
@@ -6,6 +6,7 @@
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
+using MobiusEditor.Model;
using System.Drawing;
namespace MobiusEditor.Interface
@@ -21,6 +22,6 @@ namespace MobiusEditor.Interface
Color GetBaseColor(string key);
void Load(string path);
- void Reset(GameType gameType, string theater);
+ void Reset(GameType gameType, TheaterType theater);
}
}
\ No newline at end of file
diff --git a/CnCTDRAMapEditor/MainForm.cs b/CnCTDRAMapEditor/MainForm.cs
index eb64871..ee1e5e0 100644
--- a/CnCTDRAMapEditor/MainForm.cs
+++ b/CnCTDRAMapEditor/MainForm.cs
@@ -1317,61 +1317,73 @@ namespace MobiusEditor
private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, string[] modPaths, bool noImage)
{
+ // Get plugin type
+ IGamePlugin plugin = null;
+ if (gameType == GameType.TiberianDawn)
+ {
+ plugin = new TiberianDawn.GamePluginTD(!noImage, isTdMegaMap);
+ }
+ else if (gameType == GameType.RedAlert)
+ {
+ plugin = new RedAlert.GamePluginRA(!noImage);
+ }
+ else if (gameType == GameType.SoleSurvivor)
+ {
+ plugin = new SoleSurvivor.GamePluginSS(!noImage, isTdMegaMap);
+ }
+ // Get theater object
+ TheaterTypeConverter ttc = new TheaterTypeConverter();
+ 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.TheGameTextManager.Reset(gameType);
- Globals.TheTextureManager.Reset(gameType, theater);
+ Globals.TheTextureManager.Reset(gameType, theaterType);
Globals.TheTilesetManager.Reset();
- Globals.TheTeamColorManager.Reset(gameType, theater);
- IGamePlugin plugin = null;
+ Globals.TheTeamColorManager.Reset(gameType, theaterType);
+ // Load game-specific data
if (gameType == GameType.TiberianDawn)
{
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
- AddTeamColorNone(Globals.TheTeamColorManager);
- AddTeamColorPurple(Globals.TheTeamColorManager);
- // TODO split classic and remaster team color load.
- plugin = new TiberianDawn.GamePluginTD(!noImage, isTdMegaMap);
+ AddTeamColorsTD(Globals.TheTeamColorManager);
}
else if (gameType == GameType.RedAlert)
{
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML");
Globals.TheTeamColorManager.Load("palette.cps");
- plugin = new RedAlert.GamePluginRA(!noImage);
+ AddTeamColorsRA(Globals.TheTeamColorManager);
}
else if (gameType == GameType.SoleSurvivor)
{
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
- AddTeamColorNone(Globals.TheTeamColorManager);
- AddTeamColorPurple(Globals.TheTeamColorManager);
- plugin = new SoleSurvivor.GamePluginSS(!noImage, isTdMegaMap);
+ AddTeamColorsTD(Globals.TheTeamColorManager);
}
return plugin;
}
- private static void AddTeamColorNone(ITeamColorManager teamColorManager)
+ private static void AddTeamColorsTD(ITeamColorManager teamColorManager)
{
if (teamColorManager is TeamColorManager tcm)
{
- // Add default black for unowned.
+ // Neutral
+ TeamColor teamColorSNeutral = new TeamColor(tcm);
+ teamColorSNeutral.Load(tcm.GetItem("GOOD"), "NEUTRAL");
+ tcm.AddTeamColor(teamColorSNeutral);
+ // Special
+ TeamColor teamColorSpecial = new TeamColor(tcm);
+ teamColorSpecial.Load(tcm.GetItem("GOOD"), "SPECIAL");
+ tcm.AddTeamColor(teamColorSpecial);
+ // Black for unowned.
TeamColor teamColorNone = new TeamColor(tcm);
teamColorNone.Load("NONE", "BASE_TEAM",
Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0,
new Vector3(0.30f, -1.00f, 0.00f), new Vector3(0f, 1f, 1f), new Vector2(0.0f, 0.1f),
new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(61, 61, 59));
tcm.AddTeamColor(teamColorNone);
- }
- }
-
- private static void AddTeamColorPurple(ITeamColorManager teamColorManager)
- {
- if (teamColorManager is TeamColorManager tcm)
- {
- // Add extra colors for flags.
+ // Extra colors for flags 7 and 8.
TeamColor teamColorSeven = new TeamColor(tcm);
teamColorSeven.Load(tcm.GetItem("BAD_UNIT"), "MULTI7");
tcm.AddTeamColor(teamColorSeven);
-
TeamColor teamColorEight = new TeamColor(tcm);
teamColorEight.Load("MULTI8", "BASE_TEAM",
Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0,
@@ -1381,6 +1393,17 @@ namespace MobiusEditor
}
}
+ private static void AddTeamColorsRA(ITeamColorManager teamColorManager)
+ {
+ if (teamColorManager is TeamColorManager tcm)
+ {
+ // Special
+ TeamColor teamColorSpecial = new TeamColor(tcm);
+ teamColorSpecial.Load(tcm.GetItem("SPAIN"), "SPECIAL");
+ tcm.AddTeamColor(teamColorSpecial);
+ }
+ }
+
///
/// The separate-threaded part for making a new map.
///
diff --git a/CnCTDRAMapEditor/Model/Map.cs b/CnCTDRAMapEditor/Model/Map.cs
index 5494ddf..9c94d23 100644
--- a/CnCTDRAMapEditor/Model/Map.cs
+++ b/CnCTDRAMapEditor/Model/Map.cs
@@ -472,7 +472,7 @@ namespace MobiusEditor.Model
this.Overlappers = new OverlapperSet(this.Metrics);
this.triggers = new List();
this.TeamTypes = new List();
- this.HousesIncludingNone = this.HouseTypesIncludingNone.Select(t => { var h = (House)Activator.CreateInstance(this.HouseType, t); h.SetDefault(); return h; }).ToArray();
+ this.HousesIncludingNone = this.HouseTypesIncludingNone.Select(t => { House h = (House)Activator.CreateInstance(this.HouseType, t); h.SetDefault(); return h; }).ToArray();
this.Houses = this.HousesIncludingNone.Where(h => h.Type.ID >= 0).ToArray();
this.Waypoints = waypoints.ToArray();
for (int i = 0; i < this.Waypoints.Length; ++i)
@@ -521,35 +521,35 @@ namespace MobiusEditor.Model
public void InitTheater(GameType gameType)
{
- foreach (var templateType in this.TemplateTypes.Where(itm => itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
+ foreach (TemplateType templateType in this.TemplateTypes.Where(itm => itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
templateType.Init(this.Theater);
}
- foreach (var smudgeType in this.SmudgeTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
+ foreach (SmudgeType smudgeType in this.SmudgeTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
smudgeType.Init(this.Theater);
}
- foreach (var overlayType in this.OverlayTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
+ foreach (OverlayType overlayType in this.OverlayTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
overlayType.Init(gameType, this.Theater);
}
- foreach (var terrainType in this.TerrainTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
+ foreach (TerrainType terrainType in this.TerrainTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
terrainType.Init(this.Theater);
}
// Ignore expansion status for these; they can still be enabled later.
DirectionType infDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.South).First();
- foreach (var infantryType in this.AllInfantryTypes)
+ foreach (InfantryType infantryType in this.AllInfantryTypes)
{
infantryType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), infDir);
}
DirectionType unitDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.SouthWest).First();
- foreach (var unitType in this.AllUnitTypes)
+ foreach (UnitType unitType in this.AllUnitTypes)
{
unitType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), unitDir);
}
DirectionType bldDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.North).First();
- foreach (var buildingType in this.BuildingTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
+ 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);
}
@@ -574,7 +574,7 @@ namespace MobiusEditor.Model
if (this.invalidateOverlappers)
{
this.Overlappers.Clear();
- foreach (var (location, techno) in this.Technos)
+ foreach ((Point location, ICellOccupier techno) in this.Technos)
{
if (techno is ICellOverlapper)
{
@@ -596,7 +596,7 @@ namespace MobiusEditor.Model
private int GetTotalResources(bool inBounds)
{
int totalResources = 0;
- foreach (var (cell, value) in this.Overlay)
+ foreach ((Int32 cell, Overlay value) in this.Overlay)
{
Point point;
if (!value.Type.IsResource || !this.Metrics.GetLocation(cell, out point))
@@ -622,12 +622,11 @@ namespace MobiusEditor.Model
}
}
int thickness = value.Type.IsGem ? gemStages[adj] : tiberiumStages[adj];
- // Harvesting has a bug where the final stage returns a value of 0. In case this is fixed, this is the calculation:
- //totalResources += value.Type.IsGem ? (thickness + 1) * GemValue * 4 : (thickness + 1) * TiberiumOrGoldValue;
- // Harvesting one gem stage fills one bail, plus 3 extra bails. Last stage is 0 (due to a bug), but still gets the extra bails.
+ // Harvesting has a bug where the final stage returns a value of 0 since it uses the 0-based icon index.
+ // Harvesting one gem stage fills one bail, plus 3 extra bails. Last stage is 0 (due to that bug), but still gets the extra bails.
if (Globals.ApplyHarvestBug)
{
- totalResources += value.Type.IsGem ? thickness * this.GemValue * 4 + this.GemValue * 3 : thickness * this.TiberiumOrGoldValue;
+ totalResources += value.Type.IsGem ? thickness * this.GemValue + this.GemValue * 3 : thickness * this.TiberiumOrGoldValue;
}
else
{
@@ -651,17 +650,17 @@ namespace MobiusEditor.Model
if (tiberiumOrGoldTypes.Length == 0) tiberiumOrGoldTypes = null;
OverlayType[] gemTypes = this.OverlayTypes.Where(t => t.IsGem).ToArray();
if (gemTypes.Length == 0) gemTypes = null;
- foreach (var (location, overlay) in this.Overlay.IntersectsWithPoints(locations).Where(o => o.Value.Type.IsResource))
+ foreach ((Point location, Overlay overlay) in this.Overlay.IntersectsWithPoints(locations).Where(o => o.Value.Type.IsResource))
{
int count = 0;
bool inBounds = checkBounds.Contains(location);
if (inBounds)
{
- foreach (var facing in CellMetrics.AdjacentFacings)
+ foreach (FacingType facing in CellMetrics.AdjacentFacings)
{
if (this.Metrics.Adjacent(location, facing, out Point adjacent) && checkBounds.Contains(adjacent))
{
- var adjacentOverlay = this.Overlay[adjacent];
+ Overlay adjacentOverlay = this.Overlay[adjacent];
if (adjacentOverlay?.Type.IsResource ?? false)
{
count++;
@@ -685,7 +684,7 @@ namespace MobiusEditor.Model
private void UpdateWallOverlays(ISet locations)
{
- foreach (var (location, overlay) in this.Overlay.IntersectsWithPoints(locations).Where(o => o.Value.Type.IsWall))
+ foreach ((Point location, Overlay overlay) in this.Overlay.IntersectsWithPoints(locations).Where(o => o.Value.Type.IsWall))
{
Overlay northWall = this.Overlay.Adjacent(location, FacingType.North);
Overlay eastWall = this.Overlay.Adjacent(location, FacingType.East);
@@ -714,18 +713,18 @@ namespace MobiusEditor.Model
private void UpdateConcreteOverlays(ISet locations)
{
- foreach (var (cell, overlay) in this.Overlay.IntersectsWithCells(locations).Where(o => o.Value.Type.IsConcrete))
+ foreach ((Int32 cell, Overlay overlay) in this.Overlay.IntersectsWithCells(locations).Where(o => o.Value.Type.IsConcrete))
{
- // in order: top, topnext, next, bottomnext, bottom
+ // Cells to check around the current cell. In order: top, topnext, next, bottomnext, bottom
FacingType[] even = { FacingType.North, FacingType.NorthWest, FacingType.West, FacingType.SouthWest, FacingType.South };
FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South };
int isodd = cell & 1;
- FacingType[] cells = isodd != 0 ? odd : even;
- Boolean[] conc = new bool[cells.Length];
- for (int i = 0; i < cells.Length; i++)
+ FacingType[] adjCells = isodd != 0 ? odd : even;
+ Boolean[] conc = new bool[adjCells.Length];
+ for (int i = 0; i < adjCells.Length; i++)
{
- var neighbor = this.Overlay.Adjacent(cell, cells[i]);
- if (neighbor != null && neighbor.Type == overlay.Type)
+ Overlay neighbor = this.Overlay.Adjacent(cell, adjCells[i]);
+ if (neighbor?.Type == overlay.Type)
{
int ic = overlay.Icon;
if (ic < 4 || (ic > 7 && ic < 12))
@@ -742,21 +741,26 @@ namespace MobiusEditor.Model
bool bottomnext = conc[3];
bool bottom = conc[4];
- int icon = 0;
+ int state = 0;
if (top && next || topnext && next)
{
- icon = bottom ? 1 : 5;
+ state = bottom ? 1 : 5;
}
else if (bottom && next || bottom && bottomnext)
{
- icon = topnext ? 1 : 4;
+ state = topnext ? 1 : 4;
}
else if (top && topnext)
{
- icon = 5;
+ state = 5;
}
- icon = icon == 0 ? isodd : (icon * 2) + 1 - isodd;
- overlay.Icon = icon;
+ // For some reason the odd and even icons for state 0 are swapped compared to all the others, so
+ // an extra check has to be added for that. Otherwise just "(icon * 2) + 1 - isodd" would suffice.
+ overlay.Icon = state == 0 ? isodd : (state * 2) + 1 - isodd;
+
+ // Possibly todo: add concrete to fill up corners and actually complete the logic as intended?
+ // This would require that all logic in the editor treats these corner states (2 and 3) as 'expendable',
+ // automatically overwriting them with other overlay, walls or tiberium, and not saving them in the ini.
}
}
@@ -781,7 +785,7 @@ namespace MobiusEditor.Model
private void UpdateConcreteOverlays_ORIG(ISet locations)
{
- foreach (var (cell, overlay) in this.Overlay.IntersectsWithCells(locations).Where(o => o.Value.Type.IsConcrete))
+ foreach ((Int32 cell, Overlay overlay) in this.Overlay.IntersectsWithCells(locations).Where(o => o.Value.Type.IsConcrete))
{
// Original logic as it is in the game code. Still doesn't match reality, probably due to bugs in the logic to add side cells.
FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South };
@@ -791,7 +795,7 @@ namespace MobiusEditor.Model
int index = 0;
for (int i = 0; i < cells.Length; i++)
{
- var neighbor = this.Overlay.Adjacent(cell, cells[i]);
+ Overlay neighbor = this.Overlay.Adjacent(cell, cells[i]);
if (neighbor != null && neighbor.Type == overlay.Type)
{
int ic = overlay.Icon;
@@ -1010,22 +1014,22 @@ namespace MobiusEditor.Model
return "No cell";
}
bool inBounds = this.Bounds.Contains(location);
- var sb = new StringBuilder();
+ StringBuilder sb = new StringBuilder();
sb.AppendFormat("X = {0}, Y = {1}, Cell = {2}", location.X, location.Y, cell);
- var template = this.Templates[cell];
- var templateType = template?.Type;
+ Template template = this.Templates[cell];
+ TemplateType templateType = template?.Type;
if (templateType != null)
{
sb.AppendFormat(", Template = {0} ({1})", templateType.DisplayName, template.Icon);
}
- var smudge = this.Smudge[cell];
- var smudgeType = smudge?.Type;
+ Smudge smudge = this.Smudge[cell];
+ SmudgeType smudgeType = smudge?.Type;
if (smudgeType != null)
{
sb.AppendFormat(", Smudge = {0}{1}", smudgeType.DisplayName, smudgeType.IsAutoBib ? " (Attached)" : String.Empty);
}
- var overlay = this.Overlay[cell];
- var overlayType = overlay?.Type;
+ Overlay overlay = this.Overlay[cell];
+ OverlayType overlayType = overlay?.Type;
if (overlayType != null)
{
if (overlayType.IsResource)
@@ -1037,8 +1041,8 @@ namespace MobiusEditor.Model
sb.AppendFormat(", Overlay = {0}", overlayType.DisplayName);
}
}
- var terrain = this.Technos[location] as Terrain;
- var terrainType = terrain?.Type;
+ Terrain terrain = this.Technos[location] as Terrain;
+ TerrainType terrainType = terrain?.Type;
if (terrainType != null)
{
sb.AppendFormat(", Terrain = {0}", terrainType.DisplayName);
@@ -1052,14 +1056,14 @@ namespace MobiusEditor.Model
sb.AppendFormat(", Infantry = {0} ({1})", inf.Type.DisplayName, InfantryGroup.GetStoppingTypeName(i));
}
}
- var unit = this.Technos[location] as Unit;
- var unitType = unit?.Type;
+ Unit unit = this.Technos[location] as Unit;
+ UnitType unitType = unit?.Type;
if (unitType != null)
{
sb.AppendFormat(", Unit = {0}", unitType.DisplayName);
}
- var building = this.Buildings[location] as Building;
- var buildingType = building?.Type;
+ Building building = this.Buildings[location] as Building;
+ BuildingType buildingType = building?.Type;
if (buildingType != null)
{
sb.AppendFormat(", Building = {0}", buildingType.DisplayName);
@@ -1082,8 +1086,8 @@ namespace MobiusEditor.Model
private void RemoveBibs(Building building)
{
- var bibCells = this.Smudge.IntersectsWithCells(building.BibCells).Where(x => x.Value.Type.IsAutoBib).Select(x => x.Cell).ToArray();
- foreach (var cell in bibCells)
+ Int32[] bibCells = this.Smudge.IntersectsWithCells(building.BibCells).Where(x => x.Value.Type.IsAutoBib).Select(x => x.Cell).ToArray();
+ foreach (Int32 cell in bibCells)
{
this.Smudge[cell] = null;
}
@@ -1117,7 +1121,7 @@ namespace MobiusEditor.Model
}
// This is a shallow clone; the map is new, but the placed contents all still reference the original objects.
// These shallow copies are used for map preview during editing, where dummy objects can be added without any issue.
- var map = new Map(this.BasicSection, this.Theater, this.Metrics.Size, this.HouseType, this.HouseTypesIncludingNone,
+ Map map = new Map(this.BasicSection, this.Theater, this.Metrics.Size, this.HouseType, this.HouseTypesIncludingNone,
this.FlagColors, this.TheaterTypes, this.TemplateTypes, this.TerrainTypes, this.OverlayTypes, this.SmudgeTypes,
this.EventTypes, this.CellEventTypes, this.UnitEventTypes, this.BuildingEventTypes, this.TerrainEventTypes,
this.ActionTypes, this.CellActionTypes, this.UnitActionTypes, this.BuildingActionTypes, this.TerrainActionTypes,
@@ -1139,13 +1143,13 @@ namespace MobiusEditor.Model
this.Overlay.CopyTo(map.Overlay);
this.Smudge.CopyTo(map.Smudge);
this.CellTriggers.CopyTo(map.CellTriggers);
- foreach (var (location, occupier) in this.Technos)
+ foreach ((Point location, ICellOccupier occupier) in this.Technos)
{
if (occupier is InfantryGroup infantryGroup)
{
// This creates an InfantryGroup not linked to its infantry, but it is necessary
// to ensure that the real InfantryGroups are not polluted with dummy objects.
- var newInfantryGroup = new InfantryGroup();
+ InfantryGroup newInfantryGroup = new InfantryGroup();
Array.Copy(infantryGroup.Infantry, newInfantryGroup.Infantry, newInfantryGroup.Infantry.Length);
map.Technos.Add(location, newInfantryGroup);
}
@@ -1154,7 +1158,7 @@ namespace MobiusEditor.Model
map.Technos.Add(location, occupier);
}
}
- foreach (var (location, building) in this.Buildings)
+ foreach ((Point location, ICellOccupier building) in this.Buildings)
{
// Silly side effect: this fixes any building bibs.
map.Buildings.Add(location, building);
@@ -1263,11 +1267,11 @@ namespace MobiusEditor.Model
public IEnumerable GetAllTechnos()
{
- foreach (var (location, occupier) in this.Technos)
+ foreach ((Point location, ICellOccupier occupier) in this.Technos)
{
if (occupier is InfantryGroup infantryGroup)
{
- foreach (var inf in infantryGroup.Infantry)
+ foreach (Infantry inf in infantryGroup.Infantry)
{
if (inf != null)
{
@@ -1324,9 +1328,9 @@ namespace MobiusEditor.Model
mask = bld.BaseOccupyMask;
ylen = mask.GetLength(0);
xlen = mask.GetLength(1);
- for (var y = 0; y < ylen; ++y)
+ for (Int32 y = 0; y < ylen; ++y)
{
- for (var x = 0; x < xlen; ++x)
+ for (Int32 x = 0; x < xlen; ++x)
{
if (mask[y, x])
{
@@ -1350,9 +1354,9 @@ namespace MobiusEditor.Model
mask = obj.OccupyMask;
ylen = mask.GetLength(0);
xlen = mask.GetLength(1);
- for (var y = 0; y < ylen; ++y)
+ for (Int32 y = 0; y < ylen; ++y)
{
- for (var x = 0; x < xlen; ++x)
+ for (Int32 x = 0; x < xlen; ++x)
{
if (mask[y, x])
{
@@ -1396,18 +1400,18 @@ namespace MobiusEditor.Model
mapBounds = new Rectangle(0, 0, this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight);
//locations
}
- var previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height);
- var scaledSize = new Size((int)(previewSize.Width / previewScale), (int)(previewSize.Height / previewScale));
+ Single previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height);
+ Size scaledSize = new Size((int)(previewSize.Width / previewScale), (int)(previewSize.Height / previewScale));
- using (var fullBitmap = new Bitmap(this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight))
- using (var croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height))
+ using (Bitmap fullBitmap = new Bitmap(this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight))
+ using (Bitmap croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height))
{
- using (var g = Graphics.FromImage(fullBitmap))
+ using (Graphics g = Graphics.FromImage(fullBitmap))
{
MapRenderer.SetRenderSettings(g, smooth);
MapRenderer.Render(gameType, this, g, locations, toRender, 1);
}
- using (var g = Graphics.FromImage(croppedBitmap))
+ using (Graphics g = Graphics.FromImage(croppedBitmap))
{
MapRenderer.SetRenderSettings(g, true);
Matrix transform = new Matrix();
@@ -1419,7 +1423,7 @@ namespace MobiusEditor.Model
}
if (sharpen)
{
- using (var sharpenedImage = croppedBitmap.Sharpen(1.0f))
+ using (Bitmap sharpenedImage = croppedBitmap.Sharpen(1.0f))
{
return TGA.FromBitmap(sharpenedImage);
}
@@ -1460,7 +1464,7 @@ namespace MobiusEditor.Model
{
return;
}
- foreach (var overlay in new Overlay[] { e.OldValue, e.Value })
+ foreach (Overlay overlay in new Overlay[] { e.OldValue, e.Value })
{
if (overlay == null)
{
@@ -1736,7 +1740,7 @@ namespace MobiusEditor.Model
}
}
// Clean teamtypes
- foreach (var team in this.TeamTypes)
+ foreach (TeamType team in this.TeamTypes)
{
String trig = team.Trigger;
if (!Trigger.IsEmpty(trig) && !availableUnitTriggers.Contains(trig))
@@ -1753,7 +1757,7 @@ namespace MobiusEditor.Model
}
}
// Clean triggers. Not covered in undo/redo actions since it is applied on the new list directly.
- foreach (var trig in triggers)
+ foreach (Trigger trig in triggers)
{
if (trig == null)
{
diff --git a/CnCTDRAMapEditor/Model/MapSection.cs b/CnCTDRAMapEditor/Model/MapSection.cs
index 90abf02..968f093 100644
--- a/CnCTDRAMapEditor/Model/MapSection.cs
+++ b/CnCTDRAMapEditor/Model/MapSection.cs
@@ -48,8 +48,12 @@ namespace MobiusEditor.Model
{
return null;
}
+ return ConvertFrom(context as MapContext, value as string);
+ }
- var map = (context as MapContext).Map;
+ public TheaterType ConvertFrom(MapContext context, string value)
+ {
+ var map = context.Map;
return map.TheaterTypes.Where(t => t.Equals(value)).FirstOrDefault() ?? map.TheaterTypes.First();
}
}
diff --git a/CnCTDRAMapEditor/Program.cs b/CnCTDRAMapEditor/Program.cs
index 4830605..7ff9b4c 100644
--- a/CnCTDRAMapEditor/Program.cs
+++ b/CnCTDRAMapEditor/Program.cs
@@ -255,7 +255,6 @@ namespace MobiusEditor
// Buildings
gtm["TEXT_STRUCTURE_TITLE_OIL_PUMP"] = "Oil Pump";
gtm["TEXT_STRUCTURE_TITLE_OIL_TANKER"] = "Oil Tanker";
-
String fake = " (" + gtm["TEXT_UI_FAKE"] + ")";
if (!gtm["TEXT_STRUCTURE_RA_WEAF"].EndsWith(fake)) gtm["TEXT_STRUCTURE_RA_WEAF"] = Globals.TheGameTextManager["TEXT_STRUCTURE_RA_WEAF"] + fake;
if (!gtm["TEXT_STRUCTURE_RA_FACF"].EndsWith(fake)) gtm["TEXT_STRUCTURE_RA_FACF"] = Globals.TheGameTextManager["TEXT_STRUCTURE_RA_FACF"] + fake;
@@ -272,7 +271,6 @@ namespace MobiusEditor
gtm["TEXT_OVERLAY_WCRATE"] = "Wooden Crate";
gtm["TEXT_OVERLAY_SCRATE"] = "Steel Crate";
gtm["TEXT_OVERLAY_WATER_CRATE"] = "Water Crate";
-
// Smudge
gtm["TEXT_SMUDGE_CRATER"] = "Crater";
gtm["TEXT_SMUDGE_SCORCH"] = "Scorch Mark";
diff --git a/CnCTDRAMapEditor/RedAlert/HouseTypes.cs b/CnCTDRAMapEditor/RedAlert/HouseTypes.cs
index f823454..a959bf9 100644
--- a/CnCTDRAMapEditor/RedAlert/HouseTypes.cs
+++ b/CnCTDRAMapEditor/RedAlert/HouseTypes.cs
@@ -31,8 +31,8 @@ namespace MobiusEditor.RedAlert
public static readonly HouseType Turkey = new HouseType(7, "Turkey", "TURKEY");
public static readonly HouseType Good = new HouseType(8, "GoodGuy", "GOOD");
public static readonly HouseType Bad = new HouseType(9, "BadGuy", "BAD");
- public static readonly HouseType Neutral = new HouseType(10, "Neutral");
- public static readonly HouseType Special = new HouseType(11, "Special", "SPAIN");
+ public static readonly HouseType Neutral = new HouseType(10, "Neutral", "NEUTRAL");
+ public static readonly HouseType Special = new HouseType(11, "Special", "SPECIAL");
public static readonly HouseType Multi1 = new HouseType(12, "Multi1", WaypointFlag.PlayerStart1, "MULTI1"); // yellow
public static readonly HouseType Multi2 = new HouseType(13, "Multi2", WaypointFlag.PlayerStart2, "MULTI2"); // teal
public static readonly HouseType Multi3 = new HouseType(14, "Multi3", WaypointFlag.PlayerStart3, "MULTI3"); // red
diff --git a/CnCTDRAMapEditor/TiberianDawn/HouseTypes.cs b/CnCTDRAMapEditor/TiberianDawn/HouseTypes.cs
index 6cbb387..dfc2d99 100644
--- a/CnCTDRAMapEditor/TiberianDawn/HouseTypes.cs
+++ b/CnCTDRAMapEditor/TiberianDawn/HouseTypes.cs
@@ -27,8 +27,8 @@ namespace MobiusEditor.TiberianDawn
public static readonly HouseType Good = new HouseType(0, "GoodGuy", "GOOD");
public static readonly HouseType Bad = new HouseType(1, "BadGuy", "BAD_UNIT", "BAD_STRUCTURE", ("harv", "BAD_STRUCTURE"), ("mcv", "BAD_STRUCTURE"));
// Added actual recoloring
- public static readonly HouseType Neutral = new HouseType(2, "Neutral", "GOOD");
- public static readonly HouseType Special = new HouseType(3, "Special", "GOOD");
+ public static readonly HouseType Neutral = new HouseType(2, "Neutral", "NEUTRAL");
+ public static readonly HouseType Special = new HouseType(3, "Special", "SPECIAL");
// Fixed to match actual game. Seems they messed up the naming of the colors in the xml files by taking the color definitions from the C&C
// game code in order, arbitrarily naming those "Multi1" to "Multi6", and then correctly applying those obviously wrongly named colors to
// the multi-Houses in the Remastered game. The editor code logically assumed they were named after their House, and thus got it all wrong.
diff --git a/CnCTDRAMapEditor/Utility/ArrayUtils.cs b/CnCTDRAMapEditor/Utility/ArrayUtils.cs
new file mode 100644
index 0000000..f82c7ca
--- /dev/null
+++ b/CnCTDRAMapEditor/Utility/ArrayUtils.cs
@@ -0,0 +1,374 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace MobiusEditor.Utility
+{
+ public static class ArrayUtils
+ {
+
+ public static T[][] SwapDimensions(T[][] original)
+ {
+ Int32 origHeight = original.Length;
+ if (origHeight == 0)
+ return new T[0][];
+ // Since this is for images, it is assumed that the array is a perfectly rectangular matrix
+ Int32 origWidth = original[0].Length;
+
+ T[][] swapped = new T[origWidth][];
+ for (Int32 newHeight = 0; newHeight < origWidth; ++newHeight)
+ {
+ swapped[newHeight] = new T[origHeight];
+ for (Int32 newWidth = 0; newWidth < origHeight; ++newWidth)
+ swapped[newHeight][newWidth] = original[newWidth][newHeight];
+ }
+ return swapped;
+ }
+
+ public static Boolean ArraysAreEqual(T[] arr1, T[] arr2) where T : IEquatable
+ {
+ // There's probably a Linq version of this though... Probably .All() or something.
+ // But this is with simple arrays.
+ if (arr1 == null && arr2 == null)
+ return true;
+ if (arr1 == null || arr2 == null)
+ return false;
+ Int32 arr1len = arr1.Length;
+ if (arr1len != arr2.Length)
+ return false;
+ for (Int32 i = 0; i < arr1len; ++i)
+ if (!arr1[i].Equals(arr2[i]))
+ return false;
+ return true;
+ }
+
+ ///
+ /// Creates and returns a new array, containing the contents of all the given arrays, in the given order.
+ ///
+ /// Type of the arrays
+ /// Arrays to join together.
+ /// A new array containing the contents of all given arrays, joined together.
+ public static T[] MergeArrays(params T[][] arrays)
+ {
+ Int32 length = 0;
+ Int32 nrOfArrays = arrays.Length;
+ for (Int32 i = 0; i < nrOfArrays; ++i)
+ {
+ T[] array = arrays[i];
+ if (array != null)
+ length += array.Length;
+ }
+ T[] result = new T[length];
+ Int32 copyIndex = 0;
+ for (Int32 i = 0; i < nrOfArrays; ++i)
+ {
+ T[] array = arrays[i];
+ if (array == null)
+ continue;
+ array.CopyTo(result, copyIndex);
+ copyIndex += array.Length;
+ }
+ return result;
+ }
+
+ public static T[] CloneArray(T[] array)
+ {
+ Int32 len = array.Length;
+ T[] copy = new T[len];
+ Array.Copy(array, copy, len);
+ return copy;
+ }
+
+ public static UInt64 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
+ {
+ Int32 lastByte = bytes - 1;
+ if (data.Length < startIndex + bytes)
+ throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to read a " + bytes + "-byte value at offset " + startIndex + ".");
+ UInt64 value = 0;
+ for (Int32 index = 0; index < bytes; ++index)
+ {
+ Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
+ value |= (((UInt64)data[offs]) << (8 * index));
+ }
+ return value;
+ }
+
+ #region ReadIntFromByteArray shortcuts
+ public static Int16 ReadInt16FromByteArrayLe(Byte[] data, Int32 startIndex)
+ {
+ return (Int16)ReadIntFromByteArray(data, startIndex, 2, true);
+ }
+
+ public static Int16 ReadInt16FromByteArrayBe(Byte[] data, Int32 startIndex)
+ {
+ return (Int16)ReadIntFromByteArray(data, startIndex, 2, false);
+ }
+
+ public static UInt16 ReadUInt16FromByteArrayLe(Byte[] data, Int32 startIndex)
+ {
+ return (UInt16)ReadIntFromByteArray(data, startIndex, 2, true);
+ }
+
+ public static UInt16 ReadUInt16FromByteArrayBe(Byte[] data, Int32 startIndex)
+ {
+ return (UInt16)ReadIntFromByteArray(data, startIndex, 2, false);
+ }
+
+ public static Int32 ReadInt32FromByteArrayLe(Byte[] data, Int32 startIndex)
+ {
+ return (Int32)ReadIntFromByteArray(data, startIndex, 4, true);
+ }
+
+ public static Int32 ReadInt32FromByteArrayBe(Byte[] data, Int32 startIndex)
+ {
+ return (Int32)ReadIntFromByteArray(data, startIndex, 4, false);
+ }
+
+ public static UInt32 ReadUInt32FromByteArrayLe(Byte[] data, Int32 startIndex)
+ {
+ return (UInt32)ReadIntFromByteArray(data, startIndex, 4, true);
+ }
+
+ public static UInt32 ReadUInt32FromByteArrayBe(Byte[] data, Int32 startIndex)
+ {
+ return (UInt32)ReadIntFromByteArray(data, startIndex, 4, false);
+ }
+
+ public static Int64 ReadInt64FromByteArrayLe(Byte[] data, Int32 startIndex)
+ {
+ return (Int64)ReadIntFromByteArray(data, startIndex, 8, true);
+ }
+
+ public static Int64 ReadInt64FromByteArrayBe(Byte[] data, Int32 startIndex)
+ {
+ return (Int64)ReadIntFromByteArray(data, startIndex, 8, true);
+ }
+
+ public static UInt64 ReadUInt64FromByteArrayLe(Byte[] data, Int32 startIndex)
+ {
+ return (UInt64)ReadIntFromByteArray(data, startIndex, 8, true);
+ }
+
+ public static UInt64 ReadUInt64FromByteArrayBe(Byte[] data, Int32 startIndex)
+ {
+ return (UInt64)ReadIntFromByteArray(data, startIndex, 8, true);
+ }
+ #endregion
+
+ public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt64 value)
+ {
+ Int32 lastByte = bytes - 1;
+ if (data.Length < startIndex + bytes)
+ throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
+ for (Int32 index = 0; index < bytes; ++index)
+ {
+ Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
+ data[offs] = (Byte) (value >> (8 * index) & 0xFF);
+ }
+ }
+
+ #region WriteIntToByteArray shortcuts
+ public static void WriteInt16ToByteArrayLe(Byte[] data, Int32 startIndex, Int16 value)
+ {
+ WriteIntToByteArray(data, startIndex, 2, true, (UInt64)value);
+ }
+
+ public static void WriteInt16ToByteArrayBe(Byte[] data, Int32 startIndex, Int16 value)
+ {
+ WriteIntToByteArray(data, startIndex, 2, false, (UInt64)value);
+ }
+
+ public static void WriteUInt16ToByteArrayLe(Byte[] data, Int32 startIndex, UInt16 value)
+ {
+ WriteIntToByteArray(data, startIndex, 2, true, value);
+ }
+
+ public static void WriteUInt16ToByteArrayBe(Byte[] data, Int32 startIndex, UInt16 value)
+ {
+ WriteIntToByteArray(data, startIndex, 2, false, value);
+ }
+
+ public static void WriteInt32ToByteArrayLe(Byte[] data, Int32 startIndex, Int32 value)
+ {
+ WriteIntToByteArray(data, startIndex, 4, true, (UInt64)value);
+ }
+
+ public static void WriteInt32ToByteArrayBe(Byte[] data, Int32 startIndex, Int32 value)
+ {
+ WriteIntToByteArray(data, startIndex, 4, false, (UInt64)value);
+ }
+
+ public static void WriteUInt32ToByteArrayLe(Byte[] data, Int32 startIndex, UInt32 value)
+ {
+ WriteIntToByteArray(data, startIndex, 4, true, value);
+ }
+
+ public static void WriteUInt32ToByteArrayBe(Byte[] data, Int32 startIndex, UInt32 value)
+ {
+ WriteIntToByteArray(data, startIndex, 4, false, value);
+ }
+
+ public static void WriteInt64ToByteArrayLe(Byte[] data, Int32 startIndex, Int64 value)
+ {
+ WriteIntToByteArray(data, startIndex, 8, true, (UInt64)value);
+ }
+
+ public static void WriteInt64ToByteArrayBe(Byte[] data, Int32 startIndex, Int64 value)
+ {
+ WriteIntToByteArray(data, startIndex, 8, true, (UInt64)value);
+ }
+
+ public static void WriteUInt64ToByteArrayLe(Byte[] data, Int32 startIndex, UInt64 value)
+ {
+ WriteIntToByteArray(data, startIndex, 8, true, value);
+ }
+
+ public static void WriteUInt64ToByteArrayBe(Byte[] data, Int32 startIndex, UInt64 value)
+ {
+ WriteIntToByteArray(data, startIndex, 8, true, value);
+ }
+ #endregion
+
+ public static Int32 ReadBitsFromByteArray(Byte[] dataArr, ref Int32 bitIndex, Int32 codeLen, Int32 bufferInEnd)
+ {
+ Int32 intCode = 0;
+ Int32 byteIndex = bitIndex / 8;
+ Int32 ignoreBitsAtIndex = bitIndex % 8;
+ Int32 bitsToReadAtIndex = Math.Min(codeLen, 8 - ignoreBitsAtIndex);
+ Int32 totalUsedBits = 0;
+ while (codeLen > 0)
+ {
+ if (byteIndex >= bufferInEnd)
+ return -1;
+
+ Int32 toAdd = (dataArr[byteIndex] >> ignoreBitsAtIndex) & ((1 << bitsToReadAtIndex) - 1);
+ intCode |= (toAdd << totalUsedBits);
+ totalUsedBits += bitsToReadAtIndex;
+ codeLen -= bitsToReadAtIndex;
+ bitsToReadAtIndex = Math.Min(codeLen, 8);
+ ignoreBitsAtIndex = 0;
+ byteIndex++;
+ }
+ bitIndex += totalUsedBits;
+ return intCode;
+ }
+
+ public static void WriteBitsToByteArray(Byte[] dataArr, Int32 bitIndex, Int32 codeLen, Int32 intCode)
+ {
+ Int32 byteIndex = bitIndex / 8;
+ Int32 usedBitsAtIndex = bitIndex % 8;
+ Int32 bitsToWriteAtIndex = Math.Min(codeLen, 8 - usedBitsAtIndex);
+ while (codeLen > 0)
+ {
+ Int32 codeToWrite = (intCode & ((1 << bitsToWriteAtIndex) - 1)) << usedBitsAtIndex;
+ intCode = intCode >> bitsToWriteAtIndex;
+ dataArr[byteIndex] |= (Byte) codeToWrite;
+ codeLen -= bitsToWriteAtIndex;
+ bitsToWriteAtIndex = Math.Min(codeLen, 8);
+ usedBitsAtIndex = 0;
+ byteIndex++;
+ }
+ }
+
+ public static T CloneStruct(T obj) where T : struct
+ {
+ Endianness e = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian;
+ return ReadStructFromByteArray(StructToByteArray(obj, e), 0, e);
+ }
+
+ private static void AdjustEndianness(Type type, Byte[] data, Endianness endianness, Int32 startOffset)
+ {
+ // nothing to change => return
+ if (BitConverter.IsLittleEndian == (endianness == Endianness.LittleEndian))
+ return;
+ FieldInfo[] fields = type.GetFields();
+ Int32 fieldsLen = fields.Length;
+ for (Int32 i = 0; i < fieldsLen; ++i)
+ {
+ FieldInfo field = fields[i];
+ Type fieldType = field.FieldType;
+ if (field.IsStatic)
+ // don't process static fields
+ continue;
+ if (fieldType == typeof (String))
+ // don't swap bytes for strings
+ continue;
+ Int32 offset = Marshal.OffsetOf(type, field.Name).ToInt32();
+ // handle enums
+ if (fieldType.IsEnum)
+ fieldType = Enum.GetUnderlyingType(fieldType);
+ // check for sub-fields to recurse if necessary
+ FieldInfo[] subFields = fieldType.GetFields().Where(subField => subField.IsStatic == false).ToArray();
+ Int32 effectiveOffset = startOffset + offset;
+ if (subFields.Length == 0)
+ Array.Reverse(data, effectiveOffset, Marshal.SizeOf(fieldType));
+ else // recurse
+ AdjustEndianness(fieldType, data, endianness, effectiveOffset);
+ }
+ }
+
+ public static T StructFromByteArray(Byte[] rawData, Endianness endianness) where T : struct
+ {
+ return ReadStructFromByteArray(rawData, 0, endianness);
+ }
+
+ public static T ReadStructFromByteArray(Byte[] rawData, Int32 offset, Endianness endianness) where T : struct
+ {
+ Type tType = typeof (T);
+ Int32 size = Marshal.SizeOf(tType);
+ if (size + offset > rawData.Length)
+ throw new IndexOutOfRangeException("Array is too small to get the requested struct!");
+ IntPtr ptr = IntPtr.Zero;
+ try
+ {
+ ptr = Marshal.AllocHGlobal(size);
+ // Adjust array to preferred endianness
+ AdjustEndianness(tType, rawData, endianness, offset);
+ Marshal.Copy(rawData, offset, ptr, size);
+ // Revert array to original data order
+ AdjustEndianness(tType, rawData, endianness, offset);
+ Object obj = Marshal.PtrToStructure(ptr, tType);
+ return (T) obj;
+ }
+ finally
+ {
+ if (ptr != IntPtr.Zero)
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+
+ public static Byte[] StructToByteArray(T obj, Endianness endianness) where T : struct
+ {
+ Int32 size = Marshal.SizeOf(typeof (T));
+ Byte[] target = new Byte[size];
+ WriteStructToByteArray(obj, target, 0, endianness);
+ return target;
+ }
+
+ public static void WriteStructToByteArray(T obj, Byte[] target, Int32 index, Endianness endianness) where T : struct
+ {
+ Type tType = typeof (T);
+ Int32 size = Marshal.SizeOf(tType);
+ IntPtr ptr = IntPtr.Zero;
+ try
+ {
+ ptr = Marshal.AllocHGlobal(size);
+ Marshal.StructureToPtr(obj, ptr, true);
+ Marshal.Copy(ptr, target, index, size);
+ AdjustEndianness(typeof (T), target, endianness, 0);
+ }
+ finally
+ {
+ if (ptr != IntPtr.Zero)
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+ }
+
+ public enum Endianness
+ {
+ BigEndian,
+ LittleEndian
+ }
+}
diff --git a/CnCTDRAMapEditor/Utility/ClassicSpriteLoader.cs b/CnCTDRAMapEditor/Utility/ClassicSpriteLoader.cs
new file mode 100644
index 0000000..2cf21c5
--- /dev/null
+++ b/CnCTDRAMapEditor/Utility/ClassicSpriteLoader.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MobiusEditor.Utility
+{
+ public class ClassicSpriteLoader
+ {
+
+ private static readonly Byte[] ConvertToEightBit = new Byte[64];
+
+ static ClassicSpriteLoader()
+ {
+ // Build easy lookup tables for this, so no calculations are ever needed for this later.
+ for (Int32 i = 0; i < 64; ++i)
+ ConvertToEightBit[i] = (Byte)Math.Round(i * 255.0 / 63.0, MidpointRounding.ToEven);
+ }
+
+
+ ///
+ /// Retrieves the CPS image.
+ ///
+ /// Original file data.
+ /// Start offset of the data.
+ /// The raw 8-bit linear image data in a 64000 byte array.
+ public static Bitmap LoadCpsFile(Byte[] fileData, Int32 start)
+ {
+ Byte[] imageData = GetCpsData(fileData, 0, out Color[] palette);
+ if (palette == null)
+ palette = Enumerable.Range(0, 0x100).Select(i => Color.FromArgb(i, i, i)).ToArray();
+ return ImageUtils.BuildImage(imageData, 320, 200, 200, PixelFormat.Format8bppIndexed, palette, null);
+ }
+
+ ///
+ /// Retrieves the CPS image.
+ ///
+ /// Original file data.
+ /// Start offset of the data.
+ /// The raw 8-bit linear image data in a 64000 byte array.
+ public static Byte[] GetCpsData(Byte[] fileData, Int32 start, out Color[] palette)
+ {
+ int dataLen = fileData.Length - start;
+ if (dataLen < 10)
+ throw new ArgumentException("File is not long enough to be a valid CPS file.", "fileData");
+ int fileSize = ArrayUtils.ReadInt16FromByteArrayLe(fileData, start + 0);
+ int compression = ArrayUtils.ReadUInt16FromByteArrayLe(fileData, start + 2);
+ if (compression < 0 || compression > 4)
+ throw new ArgumentException("Unknown compression type " + compression, "fileData");
+ // compressions other than 0 and 4 count the full file including size header.
+ if (compression == 0 || compression == 4)
+ fileSize += 2;
+ else
+ throw new ArgumentException("LZW and RLE compression are not supported.", "fileData");
+ if (fileSize != dataLen)
+ throw new ArgumentException("File size in header does not match!", "fileData");
+ Int32 bufferSize = ArrayUtils.ReadInt32FromByteArrayLe(fileData, start + 4);
+ Int32 paletteLength = ArrayUtils.ReadInt16FromByteArrayLe(fileData, start + 8);
+ Boolean isPc = bufferSize == 64000;
+ if (bufferSize != 64000)
+ throw new ArgumentException("Unknown CPS type.", "fileData");
+ if (paletteLength > 0)
+ {
+ Int32 palStart = start + 10;
+ if (paletteLength % 3 != 0)
+ throw new ArgumentException("Bad length for 6-bit CPS palette.", "fileData");
+ Int32 colors = paletteLength / 3;
+ palette = ReadSixBitPaletteAsEightBit(fileData, palStart, colors);
+ }
+ else
+ palette = null;
+ Byte[] imageData;
+ Int32 dataOffset = start + 10 + paletteLength;
+ if (compression == 0 && dataLen < dataOffset + bufferSize)
+ throw new ArgumentException("File is not long enough to contain the image data!", "fileData");
+ try
+ {
+ switch (compression)
+ {
+ case 0:
+ imageData = new Byte[bufferSize];
+ Array.Copy(fileData, dataOffset, imageData, 0, bufferSize);
+ break;
+ case 4:
+ imageData = new Byte[bufferSize];
+ WWCompression.LcwDecompress(fileData, ref dataOffset, imageData, 0);
+ break;
+ default:
+ throw new ArgumentException("Unsupported compression format \"" + compression + "\".", "fileData");
+ }
+ }
+ catch (Exception e)
+ {
+ throw new ArgumentException("Error decompressing image data: " + e.Message, "fileData", e);
+ }
+ if (imageData == null)
+ throw new ArgumentException("Error decompressing image data.", "fileData");
+ return imageData;
+ }
+
+ public static Color[] ReadSixBitPaletteAsEightBit(Byte[] fileData, int palStart, int colors)
+ {
+ Color[] palette = Enumerable.Repeat(Color.Black, colors).ToArray();
+
+ // Palette data should always be be 0x300 long, but this code works regardless of that.
+ int len = Math.Min(fileData.Length / 3, colors);
+ int offs = palStart;
+ for (int i = 0; i < len; ++i)
+ {
+ byte r = ConvertToEightBit[fileData[offs + 0] & 0x3F];
+ byte g = ConvertToEightBit[fileData[offs + 1] & 0x3F];
+ byte b = ConvertToEightBit[fileData[offs + 2] & 0x3F];
+ palette[i] = Color.FromArgb(r, g, b);
+ offs += 3;
+ }
+ return palette;
+ }
+ }
+}
diff --git a/CnCTDRAMapEditor/Utility/ImageUtils.cs b/CnCTDRAMapEditor/Utility/ImageUtils.cs
index b9a66e5..e2e33a0 100644
--- a/CnCTDRAMapEditor/Utility/ImageUtils.cs
+++ b/CnCTDRAMapEditor/Utility/ImageUtils.cs
@@ -402,7 +402,7 @@ namespace MobiusEditor.Utility
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
if (bitmap.PixelFormat == PixelFormat.Format8bppIndexed)
{
- return CalculateOpaqueBounds8bpp(bytes, data.Width, data.Height, data.Stride, transColors);
+ return CalculateOpaqueBounds8bpp(bytes, data.Width, data.Height, data.Stride, transColors.ToArray());
}
else
{
@@ -501,7 +501,7 @@ namespace MobiusEditor.Utility
return opaqueBounds;
}
- private static Rectangle CalculateOpaqueBounds8bpp(byte[] data, int width, int height, int stride, List transparentColors)
+ public static Rectangle CalculateOpaqueBounds8bpp(byte[] data, int width, int height, int stride, params int[] transparentColors)
{
HashSet trMap = new HashSet(transparentColors);
// Only handle 32bpp data.
diff --git a/CnCTDRAMapEditor/Utility/TeamColorManager.cs b/CnCTDRAMapEditor/Utility/TeamColorManager.cs
index 2d9d2e6..039121d 100644
--- a/CnCTDRAMapEditor/Utility/TeamColorManager.cs
+++ b/CnCTDRAMapEditor/Utility/TeamColorManager.cs
@@ -13,6 +13,7 @@
// 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.Collections.Generic;
using System.Drawing;
using System.IO;
@@ -69,7 +70,7 @@ namespace MobiusEditor.Utility
this.megafileManager = megafileManager;
}
- public void Reset(GameType gameType, string theater)
+ public void Reset(GameType gameType, TheaterType theater)
{
teamColors.Clear();
}
@@ -87,6 +88,7 @@ namespace MobiusEditor.Utility
}
if (xmlDoc != null)
{
+ teamColors.Clear();
foreach (XmlNode teamColorNode in xmlDoc.SelectNodes("/*/TeamColorTypeClass"))
{
var teamColor = new TeamColor(this);
diff --git a/CnCTDRAMapEditor/Utility/TeamRemap.cs b/CnCTDRAMapEditor/Utility/TeamRemap.cs
index f66ce04..017716d 100644
--- a/CnCTDRAMapEditor/Utility/TeamRemap.cs
+++ b/CnCTDRAMapEditor/Utility/TeamRemap.cs
@@ -2,7 +2,9 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.Drawing.Imaging;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
@@ -16,6 +18,15 @@ namespace MobiusEditor.Utility
private byte[] remapTable;
+ public TeamRemap(string newName, TeamRemap baseRemap)
+ {
+ this.Name = newName;
+ this.UnitRadarColor = baseRemap.UnitRadarColor;
+ this.BuildingRadarColor = baseRemap.BuildingRadarColor;
+ this.remapTable = new byte[0x100];
+ Array.Copy(baseRemap.remapTable, remapTable, remapTable.Length);
+ }
+
public TeamRemap(string name, byte unitRadarColor, byte buildingRadarColor, byte remapstart, byte[] remapValues)
{
this.Name = name;
@@ -42,15 +53,66 @@ namespace MobiusEditor.Utility
}
}
-
public void ApplyToImage(Bitmap image)
{
- throw new NotImplementedException();
+ ApplyToImage(image, out _);
}
public void ApplyToImage(Bitmap image, out Rectangle opaqueBounds)
{
- throw new NotImplementedException();
+ if (image == null)
+ {
+ opaqueBounds = Rectangle.Empty;
+ return;
+ }
+ int bytesPerPixel = Image.GetPixelFormatSize(image.PixelFormat) / 8;
+ if (bytesPerPixel != 3 && bytesPerPixel != 4)
+ {
+ opaqueBounds = new Rectangle(Point.Empty, image.Size);
+ return;
+ }
+ BitmapData data = null;
+ try
+ {
+ data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, image.PixelFormat);
+ byte[] bytes = new byte[data.Stride * data.Height];
+ Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
+ int width = data.Width;
+ int height = data.Height;
+ int stride = data.Stride;
+ opaqueBounds = ImageUtils.CalculateOpaqueBounds8bpp(bytes, width, height, stride, 0);
+ ApplyToImage(bytes, width, height, bytesPerPixel, stride, opaqueBounds);
+ Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
+ }
+ finally
+ {
+ if (data != null)
+ {
+ image.UnlockBits(data);
+ }
+ }
+ }
+ public void ApplyToImage(byte[] bytes, int width, int height, int bytesPerPixel, int stride, Rectangle? opaqueBounds)
+ {
+ // Only handle 8bpp data.
+ if (bytesPerPixel != 1)
+ {
+ return;
+ }
+ Rectangle bounds = opaqueBounds ?? new Rectangle(0, 0, width, height);
+ int boundsBottom = Math.Min(height, bounds.Bottom);
+ int boundsWidth = Math.Min(Math.Max(0, width - bounds.Left), bounds.Width);
+ int linePtr = 0;
+ for (int y = bounds.Top; y < boundsBottom; y++)
+ {
+ int ptr = linePtr + bounds.Left;
+ for (int x = 0; x < boundsWidth; x++)
+ {
+ bytes[ptr] = remapTable[bytes[ptr]];
+ ptr++;
+ }
+ linePtr += stride;
+ }
}
}
}
diff --git a/CnCTDRAMapEditor/Utility/TeamRemapManager.cs b/CnCTDRAMapEditor/Utility/TeamRemapManager.cs
index d0eaab9..585379b 100644
--- a/CnCTDRAMapEditor/Utility/TeamRemapManager.cs
+++ b/CnCTDRAMapEditor/Utility/TeamRemapManager.cs
@@ -1,55 +1,74 @@
using MobiusEditor.Interface;
+using MobiusEditor.Model;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
namespace MobiusEditor.Utility
{
class TeamRemapManager : ITeamColorManager
{
// TD remap
- public static readonly TeamRemap RemapTdGoodGuy = new TeamRemap("GOOD", 176, 180, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 });
- public static readonly TeamRemap RemapTdBadGuyRed = new TeamRemap("BAD_STRUCTURE", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 });
- public static readonly TeamRemap RemapTdBadGuyGray = new TeamRemap("BAD_UNIT", 127, 123, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 });
+ public static readonly TeamRemap RemapTdGood = new TeamRemap("GOOD", 176, 180, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 });
+ public static readonly TeamRemap RemapTdBadRed = new TeamRemap("BAD_STRUCTURE", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 });
+ public static readonly TeamRemap RemapTdBadGray = new TeamRemap("BAD_UNIT", 127, 123, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 });
// The color name identifiers in the remaster are all out of wack.
- public static readonly TeamRemap RemapTdBlue = new TeamRemap("MULTI2", 2, 135, 176, new byte[] {2, 119, 118, 135, 136, 138, 112, 12, 118, 135, 136, 137, 138, 139, 114, 112});
+ public static readonly TeamRemap RemapTdBlue = new TeamRemap("MULTI2", 2, 135, 176, new byte[] { 2, 119, 118, 135, 136, 138, 112, 12, 118, 135, 136, 137, 138, 139, 114, 112 });
public static readonly TeamRemap RemapTdOrange = new TeamRemap("MULTI5", 24, 26, 176, new byte[] { 24, 25, 26, 27, 29, 31, 46, 47, 26, 27, 28, 29, 30, 31, 43, 47 });
public static readonly TeamRemap RemapTdGreen = new TeamRemap("MULTI4", 167, 159, 176, new byte[] { 5, 165, 166, 167, 159, 142, 140, 199, 166, 167, 157, 3, 159, 143, 142, 141 });
public static readonly TeamRemap RemapTdLtBlue = new TeamRemap("MULTI6", 201, 203, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 });
public static readonly TeamRemap RemapTdYellow = new TeamRemap("MULTI1", 5, 157, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 });
public static readonly TeamRemap RemapTdRed = new TeamRemap("MULTI3", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 });
// Added extra colours for flags
- public static readonly TeamRemap RemapTdMul7 = new TeamRemap("MULTI7", 146, 209, 176, new byte[] { 146, 152, 209, 151, 173, 150, 173, 183, 146, 152, 209, 151, 173, 150, 173, 183 }); // Brown
- public static readonly TeamRemap RemapTdMul8 = new TeamRemap("MULTI8", 214, 213, 176, new byte[] { 132, 133, 134, 213, 214, 121, 120, 12, 133, 134, 213, 214, 121, 174, 120, 199 }); // Burgundy
- public static readonly TeamRemap RemapTdNone = new TeamRemap("NONE", 199, 199, 176, new byte[] { 14, 195, 196, 13, 169, 198, 199, 112, 14, 195, 196, 13, 169, 198, 199, 112 }); // Black
-
+ public static readonly TeamRemap RemapTdBrown = new TeamRemap("MULTI7", 146, 209, 176, new byte[] { 146, 152, 209, 151, 173, 150, 173, 183, 146, 152, 209, 151, 173, 150, 173, 183 }); // Brown
+ //public static readonly TeamRemap RemapTdBurg = new TeamRemap("Burgudy", 214, 213, 176, new byte[] { 132, 133, 134, 213, 214, 121, 120, 12, 133, 134, 213, 214, 121, 174, 120, 199 }); // Burgundy
+ public static readonly TeamRemap RemapTdPink = new TeamRemap("MULTI8", 217, 218, 176, new byte[] { 17, 17, 217, 218, 209, 213, 174, 120, 217, 217, 218, 209, 213, 214, 214, 174 }); // Pink
+ public static readonly TeamRemap RemapTdBlack = new TeamRemap("NONE", 199, 199, 176, new byte[] { 14, 195, 196, 13, 169, 198, 199, 112, 14, 195, 196, 13, 169, 198, 199, 112 }); // Black
private static readonly Dictionary RemapsTd;
- public const Byte MaxValueSixBit = 63;
- private static readonly Byte[] ConvertToEightBit = new Byte[64];
-
static TeamRemapManager()
{
RemapsTd = (from field in typeof(ITeamColorManager).GetFields(BindingFlags.Static | BindingFlags.Public)
where field.IsInitOnly && typeof(TeamRemap).IsAssignableFrom(field.FieldType) && field.Name.StartsWith("RemapTd")
select field.GetValue(null) as TeamRemap).ToDictionary(trm => trm.Name);
-
- // Build easy lookup tables for this, so no calculations are ever needed for this later.
- for (Int32 i = 0; i < 64; ++i)
- ConvertToEightBit[i] = (Byte)Math.Round(i * 255.0 / 63.0, MidpointRounding.ToEven);
}
private Dictionary remapsRa = new Dictionary();
private GameType currentlyLoadedGameType;
private Color[] currentlyLoadedPalette;
private readonly IArchiveManager mixfileManager;
+ private readonly string[] remapsColorsRa =
+ {
+ "GOLD",
+ "LTBLUE",
+ "RED",
+ "GREEN",
+ "ORANGE",
+ "GREY",
+ "BLUE",
+ "BROWN",
+ //"TYPE",
+ //"REALLY_BLUE",
+ //"DIALOG_BLUE",
+ };
+ private readonly Dictionary remapUseRa = new Dictionary {
+ { "GOLD", new string[]{ "SPAIN", "NEUTRAL", "SPECIAL" , "MULTI1" } },
+ { "LTBLUE", new string[]{ "GREECE", "GOOD", "MULTI2" } },
+ { "RED", new string[]{ "USSR", "BAD", "MULTI3" } },
+ { "GREEN", new string[]{ "ENGLAND", "MULTI4" } },
+ { "ORANGE", new string[]{ "UKRAINE", "MULTI5" } },
+ { "GREY", new string[]{ "GERMANY", "MULTI6" } },
+ { "BLUE", new string[]{ "FRANCE", "MULTI7" } },
+ { "BROWN", new string[]{ "TURKEY", "MULTI8" } },
+ //{ "TYPE", new string[]{ } },
+ //{ "REALLY_BLUE", new string[]{ } },
+ //{ "DIALOG_BLUE", new string[]{ } },
+ };
public ITeamColor this[string key] => GetforCurrentGame(key);
public Color GetBaseColor(string key)
@@ -89,7 +108,64 @@ namespace MobiusEditor.Utility
public void Load(string path)
{
- // Check if CPS, if so, fill in "this.remapsRa"
+ if (currentlyLoadedGameType != GameType.RedAlert)
+ {
+ return;
+ }
+ byte[] cpsData;
+ Color[] palette;
+ using (Stream palettecps = mixfileManager.OpenFile(path))
+ {
+ if (palettecps == null)
+ {
+ return;
+ }
+ try
+ {
+ Byte[] cpsFileBytes;
+ using (BinaryReader sr = new BinaryReader(palettecps))
+ {
+ cpsFileBytes = GeneralUtils.ReadAllBytes(sr);
+ }
+ cpsData = ClassicSpriteLoader.GetCpsData(cpsFileBytes, 0, out palette);
+ }
+ catch (ArgumentException ex)
+ {
+ return;
+ }
+ }
+ // Data found; re-initialise RA remaps.
+ this.remapsRa.Clear();
+ int height = Math.Min(200, remapsColorsRa.Length);
+ Dictionary raRemapColors = new Dictionary();
+ byte[] remapSource = new byte[16];
+ Array.Copy(cpsData, 0, remapSource, 0, 16);
+ for (int y = 0; y < height; ++y)
+ {
+ int ptr = 320 * y;
+ String name = remapsColorsRa[y];
+ Byte[] remap = new byte[16];
+ Array.Copy(cpsData, ptr, remap, 0, 16);
+ // Apparently the same in RA?
+ byte unitRadarColor = cpsData[ptr + 6];
+ byte buildingRadarColor = cpsData[ptr + 6];
+ TeamRemap col = new TeamRemap(name, unitRadarColor, buildingRadarColor, remapSource, remap);
+ raRemapColors.Add(name, col);
+ }
+ foreach (String col in remapsColorsRa)
+ {
+ string[] usedRemaps;
+ TeamRemap remapColor;
+ if (remapUseRa.TryGetValue(col, out usedRemaps) && raRemapColors.TryGetValue(col, out remapColor))
+ {
+ for (int i = 0; i < usedRemaps.Length; ++i)
+ {
+ String actualName = usedRemaps[i];
+ TeamRemap actualCol = new TeamRemap(usedRemaps[i], remapColor);
+ remapsRa.Add(actualName, actualCol);
+ }
+ }
+ }
}
public TeamRemapManager(IArchiveManager fileManager)
@@ -97,41 +173,32 @@ namespace MobiusEditor.Utility
this.mixfileManager = fileManager;
}
- public void Reset(GameType gameType, string theater)
+ public void Reset(GameType gameType, TheaterType theater)
{
+ this.remapsRa.Clear();
currentlyLoadedGameType = gameType;
- currentlyLoadedPalette = new Color[0x100];
// file manager should already be reset to read from the correct game at this point.
- using (Stream palette = mixfileManager.OpenFile(theater + ".pal"))
+ using (Stream palette = mixfileManager.OpenFile(theater.ClassicTileset + ".pal"))
{
- if (palette != null)
+ if (palette == null)
+ {
+ // Grayscale palette; looks awful but still allows distinguishing stuff.
+ currentlyLoadedPalette = Enumerable.Range(0, 0x100).Select(i => Color.FromArgb(i, i, i)).ToArray();
+ }
+ else
{
byte[] pal;
using (BinaryReader sr = new BinaryReader(palette))
{
pal = GeneralUtils.ReadAllBytes(sr);
}
- int len = Math.Min(pal.Length / 3, 0x100);
- int offs = 0;
- for (int i = 0; i < len; ++i)
- {
- byte r = ConvertToEightBit[pal[offs + 0] & 0x3F];
- byte g = ConvertToEightBit[pal[offs + 1] & 0x3F];
- byte b = ConvertToEightBit[pal[offs + 2] & 0x3F];
- currentlyLoadedPalette[i] = Color.FromArgb(r, g, b);
- offs += 3;
- }
+ currentlyLoadedPalette = ClassicSpriteLoader.ReadSixBitPaletteAsEightBit(pal, 0, 0x100);
}
}
- if (gameType == GameType.TiberianDawn || gameType == GameType.SoleSurvivor)
- {
-
- }
- }
-
- public void setTheater()
- {
-
+ // Set background transparent
+ currentlyLoadedPalette[0] = Color.FromArgb(0x00, currentlyLoadedPalette[0]);
+ // Set shadow color to semitransparent black. I'm not gonna mess around with classic fading table remapping for this.
+ currentlyLoadedPalette[4] = Color.FromArgb(0x80, Color.Black);
}
}
}
diff --git a/CnCTDRAMapEditor/Utility/TextureManager.cs b/CnCTDRAMapEditor/Utility/TextureManager.cs
index 03b1979..a8a4772 100644
--- a/CnCTDRAMapEditor/Utility/TextureManager.cs
+++ b/CnCTDRAMapEditor/Utility/TextureManager.cs
@@ -24,6 +24,7 @@ using System.IO.Compression;
using System.Runtime.InteropServices;
using TGASharpLib;
using MobiusEditor.Interface;
+using MobiusEditor.Model;
namespace MobiusEditor.Utility
{
@@ -42,7 +43,7 @@ namespace MobiusEditor.Utility
this.megafileManager = megafileManager;
}
- public void Reset(GameType gameType, string theater)
+ public void Reset(GameType gameType, TheaterType theater)
{
Bitmap[] cachedImages = cachedTextures.Values.ToArray();
cachedTextures.Clear();