* Fixed gem value calculation

* Added all code for classic color handling
This commit is contained in:
Nyerguds 2023-06-09 01:13:25 +02:00
parent 303762eb31
commit 58be2ebd90
15 changed files with 806 additions and 146 deletions

View File

@ -583,6 +583,7 @@
<Compile Include="Tools\Dialogs\WaypointsToolDialog.Designer.cs"> <Compile Include="Tools\Dialogs\WaypointsToolDialog.Designer.cs">
<DependentUpon>WaypointsToolDialog.cs</DependentUpon> <DependentUpon>WaypointsToolDialog.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Utility\ArrayUtils.cs" />
<Compile Include="Utility\BlobDetection.cs" /> <Compile Include="Utility\BlobDetection.cs" />
<Compile Include="Tools\InfantryTool.cs" /> <Compile Include="Tools\InfantryTool.cs" />
<Compile Include="Tools\OverlaysTool.cs" /> <Compile Include="Tools\OverlaysTool.cs" />
@ -596,6 +597,7 @@
<Compile Include="Tools\CellTriggersTool.cs" /> <Compile Include="Tools\CellTriggersTool.cs" />
<Compile Include="Tools\WaypointsTool.cs" /> <Compile Include="Tools\WaypointsTool.cs" />
<Compile Include="Utility\BooleanStringStyle.cs" /> <Compile Include="Utility\BooleanStringStyle.cs" />
<Compile Include="Utility\ClassicSpriteLoader.cs" />
<Compile Include="Utility\CRC.cs" /> <Compile Include="Utility\CRC.cs" />
<Compile Include="Utility\ExtensionMethods.cs" /> <Compile Include="Utility\ExtensionMethods.cs" />
<Compile Include="Utility\GameTextManager.cs" /> <Compile Include="Utility\GameTextManager.cs" />

View File

@ -6,6 +6,7 @@
// the GNU General Public License as published by the Free Software Foundation, // 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. // either version 3 of the License, or (at your option) any later version.
using MobiusEditor.Model;
using System.Drawing; using System.Drawing;
namespace MobiusEditor.Interface namespace MobiusEditor.Interface
@ -21,6 +22,6 @@ namespace MobiusEditor.Interface
Color GetBaseColor(string key); Color GetBaseColor(string key);
void Load(string path); void Load(string path);
void Reset(GameType gameType, string theater); void Reset(GameType gameType, TheaterType theater);
} }
} }

View File

@ -1317,61 +1317,73 @@ namespace MobiusEditor
private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, string[] modPaths, bool noImage) 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. // Resetting to a specific game type will take care of classic mode.
Globals.TheArchiveManager.ExpandModPaths = modPaths; Globals.TheArchiveManager.ExpandModPaths = modPaths;
Globals.TheArchiveManager.Reset(gameType); Globals.TheArchiveManager.Reset(gameType);
Globals.TheGameTextManager.Reset(gameType); Globals.TheGameTextManager.Reset(gameType);
Globals.TheTextureManager.Reset(gameType, theater); Globals.TheTextureManager.Reset(gameType, theaterType);
Globals.TheTilesetManager.Reset(); Globals.TheTilesetManager.Reset();
Globals.TheTeamColorManager.Reset(gameType, theater); Globals.TheTeamColorManager.Reset(gameType, theaterType);
IGamePlugin plugin = null; // Load game-specific data
if (gameType == GameType.TiberianDawn) if (gameType == GameType.TiberianDawn)
{ {
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML"); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
AddTeamColorNone(Globals.TheTeamColorManager); AddTeamColorsTD(Globals.TheTeamColorManager);
AddTeamColorPurple(Globals.TheTeamColorManager);
// TODO split classic and remaster team color load.
plugin = new TiberianDawn.GamePluginTD(!noImage, isTdMegaMap);
} }
else if (gameType == GameType.RedAlert) else if (gameType == GameType.RedAlert)
{ {
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML"); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML");
Globals.TheTeamColorManager.Load("palette.cps"); Globals.TheTeamColorManager.Load("palette.cps");
plugin = new RedAlert.GamePluginRA(!noImage); AddTeamColorsRA(Globals.TheTeamColorManager);
} }
else if (gameType == GameType.SoleSurvivor) else if (gameType == GameType.SoleSurvivor)
{ {
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML"); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
AddTeamColorNone(Globals.TheTeamColorManager); AddTeamColorsTD(Globals.TheTeamColorManager);
AddTeamColorPurple(Globals.TheTeamColorManager);
plugin = new SoleSurvivor.GamePluginSS(!noImage, isTdMegaMap);
} }
return plugin; return plugin;
} }
private static void AddTeamColorNone(ITeamColorManager teamColorManager) private static void AddTeamColorsTD(ITeamColorManager teamColorManager)
{ {
if (teamColorManager is TeamColorManager tcm) 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); TeamColor teamColorNone = new TeamColor(tcm);
teamColorNone.Load("NONE", "BASE_TEAM", teamColorNone.Load("NONE", "BASE_TEAM",
Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0, 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.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)); new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(61, 61, 59));
tcm.AddTeamColor(teamColorNone); tcm.AddTeamColor(teamColorNone);
} // Extra colors for flags 7 and 8.
}
private static void AddTeamColorPurple(ITeamColorManager teamColorManager)
{
if (teamColorManager is TeamColorManager tcm)
{
// Add extra colors for flags.
TeamColor teamColorSeven = new TeamColor(tcm); TeamColor teamColorSeven = new TeamColor(tcm);
teamColorSeven.Load(tcm.GetItem("BAD_UNIT"), "MULTI7"); teamColorSeven.Load(tcm.GetItem("BAD_UNIT"), "MULTI7");
tcm.AddTeamColor(teamColorSeven); tcm.AddTeamColor(teamColorSeven);
TeamColor teamColorEight = new TeamColor(tcm); TeamColor teamColorEight = new TeamColor(tcm);
teamColorEight.Load("MULTI8", "BASE_TEAM", teamColorEight.Load("MULTI8", "BASE_TEAM",
Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0, 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);
}
}
/// <summary> /// <summary>
/// The separate-threaded part for making a new map. /// The separate-threaded part for making a new map.
/// </summary> /// </summary>

View File

@ -472,7 +472,7 @@ namespace MobiusEditor.Model
this.Overlappers = new OverlapperSet<ICellOverlapper>(this.Metrics); this.Overlappers = new OverlapperSet<ICellOverlapper>(this.Metrics);
this.triggers = new List<Trigger>(); this.triggers = new List<Trigger>();
this.TeamTypes = new List<TeamType>(); this.TeamTypes = new List<TeamType>();
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.Houses = this.HousesIncludingNone.Where(h => h.Type.ID >= 0).ToArray();
this.Waypoints = waypoints.ToArray(); this.Waypoints = waypoints.ToArray();
for (int i = 0; i < this.Waypoints.Length; ++i) for (int i = 0; i < this.Waypoints.Length; ++i)
@ -521,35 +521,35 @@ namespace MobiusEditor.Model
public void InitTheater(GameType gameType) 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); 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); 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); 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); terrainType.Init(this.Theater);
} }
// Ignore expansion status for these; they can still be enabled later. // Ignore expansion status for these; they can still be enabled later.
DirectionType infDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.South).First(); 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); 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(); 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); 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(); 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); 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) if (this.invalidateOverlappers)
{ {
this.Overlappers.Clear(); this.Overlappers.Clear();
foreach (var (location, techno) in this.Technos) foreach ((Point location, ICellOccupier techno) in this.Technos)
{ {
if (techno is ICellOverlapper) if (techno is ICellOverlapper)
{ {
@ -596,7 +596,7 @@ namespace MobiusEditor.Model
private int GetTotalResources(bool inBounds) private int GetTotalResources(bool inBounds)
{ {
int totalResources = 0; int totalResources = 0;
foreach (var (cell, value) in this.Overlay) foreach ((Int32 cell, Overlay value) in this.Overlay)
{ {
Point point; Point point;
if (!value.Type.IsResource || !this.Metrics.GetLocation(cell, out 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]; 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: // Harvesting has a bug where the final stage returns a value of 0 since it uses the 0-based icon index.
//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 that bug), but still gets the extra bails.
// 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.
if (Globals.ApplyHarvestBug) 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 else
{ {
@ -651,17 +650,17 @@ namespace MobiusEditor.Model
if (tiberiumOrGoldTypes.Length == 0) tiberiumOrGoldTypes = null; if (tiberiumOrGoldTypes.Length == 0) tiberiumOrGoldTypes = null;
OverlayType[] gemTypes = this.OverlayTypes.Where(t => t.IsGem).ToArray(); OverlayType[] gemTypes = this.OverlayTypes.Where(t => t.IsGem).ToArray();
if (gemTypes.Length == 0) gemTypes = null; 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; int count = 0;
bool inBounds = checkBounds.Contains(location); bool inBounds = checkBounds.Contains(location);
if (inBounds) 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)) 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) if (adjacentOverlay?.Type.IsResource ?? false)
{ {
count++; count++;
@ -685,7 +684,7 @@ namespace MobiusEditor.Model
private void UpdateWallOverlays(ISet<Point> locations) private void UpdateWallOverlays(ISet<Point> 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 northWall = this.Overlay.Adjacent(location, FacingType.North);
Overlay eastWall = this.Overlay.Adjacent(location, FacingType.East); Overlay eastWall = this.Overlay.Adjacent(location, FacingType.East);
@ -714,18 +713,18 @@ namespace MobiusEditor.Model
private void UpdateConcreteOverlays(ISet<Point> locations) private void UpdateConcreteOverlays(ISet<Point> 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[] even = { FacingType.North, FacingType.NorthWest, FacingType.West, FacingType.SouthWest, FacingType.South };
FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South }; FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South };
int isodd = cell & 1; int isodd = cell & 1;
FacingType[] cells = isodd != 0 ? odd : even; FacingType[] adjCells = isodd != 0 ? odd : even;
Boolean[] conc = new bool[cells.Length]; Boolean[] conc = new bool[adjCells.Length];
for (int i = 0; i < cells.Length; i++) for (int i = 0; i < adjCells.Length; i++)
{ {
var neighbor = this.Overlay.Adjacent(cell, cells[i]); Overlay neighbor = this.Overlay.Adjacent(cell, adjCells[i]);
if (neighbor != null && neighbor.Type == overlay.Type) if (neighbor?.Type == overlay.Type)
{ {
int ic = overlay.Icon; int ic = overlay.Icon;
if (ic < 4 || (ic > 7 && ic < 12)) if (ic < 4 || (ic > 7 && ic < 12))
@ -742,21 +741,26 @@ namespace MobiusEditor.Model
bool bottomnext = conc[3]; bool bottomnext = conc[3];
bool bottom = conc[4]; bool bottom = conc[4];
int icon = 0; int state = 0;
if (top && next || topnext && next) if (top && next || topnext && next)
{ {
icon = bottom ? 1 : 5; state = bottom ? 1 : 5;
} }
else if (bottom && next || bottom && bottomnext) else if (bottom && next || bottom && bottomnext)
{ {
icon = topnext ? 1 : 4; state = topnext ? 1 : 4;
} }
else if (top && topnext) else if (top && topnext)
{ {
icon = 5; state = 5;
} }
icon = icon == 0 ? isodd : (icon * 2) + 1 - isodd; // For some reason the odd and even icons for state 0 are swapped compared to all the others, so
overlay.Icon = icon; // 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<Point> locations) private void UpdateConcreteOverlays_ORIG(ISet<Point> 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. // 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 }; FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South };
@ -791,7 +795,7 @@ namespace MobiusEditor.Model
int index = 0; int index = 0;
for (int i = 0; i < cells.Length; i++) 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) if (neighbor != null && neighbor.Type == overlay.Type)
{ {
int ic = overlay.Icon; int ic = overlay.Icon;
@ -1010,22 +1014,22 @@ namespace MobiusEditor.Model
return "No cell"; return "No cell";
} }
bool inBounds = this.Bounds.Contains(location); 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); sb.AppendFormat("X = {0}, Y = {1}, Cell = {2}", location.X, location.Y, cell);
var template = this.Templates[cell]; Template template = this.Templates[cell];
var templateType = template?.Type; TemplateType templateType = template?.Type;
if (templateType != null) if (templateType != null)
{ {
sb.AppendFormat(", Template = {0} ({1})", templateType.DisplayName, template.Icon); sb.AppendFormat(", Template = {0} ({1})", templateType.DisplayName, template.Icon);
} }
var smudge = this.Smudge[cell]; Smudge smudge = this.Smudge[cell];
var smudgeType = smudge?.Type; SmudgeType smudgeType = smudge?.Type;
if (smudgeType != null) if (smudgeType != null)
{ {
sb.AppendFormat(", Smudge = {0}{1}", smudgeType.DisplayName, smudgeType.IsAutoBib ? " (Attached)" : String.Empty); sb.AppendFormat(", Smudge = {0}{1}", smudgeType.DisplayName, smudgeType.IsAutoBib ? " (Attached)" : String.Empty);
} }
var overlay = this.Overlay[cell]; Overlay overlay = this.Overlay[cell];
var overlayType = overlay?.Type; OverlayType overlayType = overlay?.Type;
if (overlayType != null) if (overlayType != null)
{ {
if (overlayType.IsResource) if (overlayType.IsResource)
@ -1037,8 +1041,8 @@ namespace MobiusEditor.Model
sb.AppendFormat(", Overlay = {0}", overlayType.DisplayName); sb.AppendFormat(", Overlay = {0}", overlayType.DisplayName);
} }
} }
var terrain = this.Technos[location] as Terrain; Terrain terrain = this.Technos[location] as Terrain;
var terrainType = terrain?.Type; TerrainType terrainType = terrain?.Type;
if (terrainType != null) if (terrainType != null)
{ {
sb.AppendFormat(", Terrain = {0}", terrainType.DisplayName); sb.AppendFormat(", Terrain = {0}", terrainType.DisplayName);
@ -1052,14 +1056,14 @@ namespace MobiusEditor.Model
sb.AppendFormat(", Infantry = {0} ({1})", inf.Type.DisplayName, InfantryGroup.GetStoppingTypeName(i)); sb.AppendFormat(", Infantry = {0} ({1})", inf.Type.DisplayName, InfantryGroup.GetStoppingTypeName(i));
} }
} }
var unit = this.Technos[location] as Unit; Unit unit = this.Technos[location] as Unit;
var unitType = unit?.Type; UnitType unitType = unit?.Type;
if (unitType != null) if (unitType != null)
{ {
sb.AppendFormat(", Unit = {0}", unitType.DisplayName); sb.AppendFormat(", Unit = {0}", unitType.DisplayName);
} }
var building = this.Buildings[location] as Building; Building building = this.Buildings[location] as Building;
var buildingType = building?.Type; BuildingType buildingType = building?.Type;
if (buildingType != null) if (buildingType != null)
{ {
sb.AppendFormat(", Building = {0}", buildingType.DisplayName); sb.AppendFormat(", Building = {0}", buildingType.DisplayName);
@ -1082,8 +1086,8 @@ namespace MobiusEditor.Model
private void RemoveBibs(Building building) private void RemoveBibs(Building building)
{ {
var bibCells = this.Smudge.IntersectsWithCells(building.BibCells).Where(x => x.Value.Type.IsAutoBib).Select(x => x.Cell).ToArray(); Int32[] bibCells = this.Smudge.IntersectsWithCells(building.BibCells).Where(x => x.Value.Type.IsAutoBib).Select(x => x.Cell).ToArray();
foreach (var cell in bibCells) foreach (Int32 cell in bibCells)
{ {
this.Smudge[cell] = null; 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. // 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. // 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.FlagColors, this.TheaterTypes, this.TemplateTypes, this.TerrainTypes, this.OverlayTypes, this.SmudgeTypes,
this.EventTypes, this.CellEventTypes, this.UnitEventTypes, this.BuildingEventTypes, this.TerrainEventTypes, this.EventTypes, this.CellEventTypes, this.UnitEventTypes, this.BuildingEventTypes, this.TerrainEventTypes,
this.ActionTypes, this.CellActionTypes, this.UnitActionTypes, this.BuildingActionTypes, this.TerrainActionTypes, this.ActionTypes, this.CellActionTypes, this.UnitActionTypes, this.BuildingActionTypes, this.TerrainActionTypes,
@ -1139,13 +1143,13 @@ namespace MobiusEditor.Model
this.Overlay.CopyTo(map.Overlay); this.Overlay.CopyTo(map.Overlay);
this.Smudge.CopyTo(map.Smudge); this.Smudge.CopyTo(map.Smudge);
this.CellTriggers.CopyTo(map.CellTriggers); 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) if (occupier is InfantryGroup infantryGroup)
{ {
// This creates an InfantryGroup not linked to its infantry, but it is necessary // 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. // 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); Array.Copy(infantryGroup.Infantry, newInfantryGroup.Infantry, newInfantryGroup.Infantry.Length);
map.Technos.Add(location, newInfantryGroup); map.Technos.Add(location, newInfantryGroup);
} }
@ -1154,7 +1158,7 @@ namespace MobiusEditor.Model
map.Technos.Add(location, occupier); 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. // Silly side effect: this fixes any building bibs.
map.Buildings.Add(location, building); map.Buildings.Add(location, building);
@ -1263,11 +1267,11 @@ namespace MobiusEditor.Model
public IEnumerable<ITechno> GetAllTechnos() public IEnumerable<ITechno> GetAllTechnos()
{ {
foreach (var (location, occupier) in this.Technos) foreach ((Point location, ICellOccupier occupier) in this.Technos)
{ {
if (occupier is InfantryGroup infantryGroup) if (occupier is InfantryGroup infantryGroup)
{ {
foreach (var inf in infantryGroup.Infantry) foreach (Infantry inf in infantryGroup.Infantry)
{ {
if (inf != null) if (inf != null)
{ {
@ -1324,9 +1328,9 @@ namespace MobiusEditor.Model
mask = bld.BaseOccupyMask; mask = bld.BaseOccupyMask;
ylen = mask.GetLength(0); ylen = mask.GetLength(0);
xlen = mask.GetLength(1); 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]) if (mask[y, x])
{ {
@ -1350,9 +1354,9 @@ namespace MobiusEditor.Model
mask = obj.OccupyMask; mask = obj.OccupyMask;
ylen = mask.GetLength(0); ylen = mask.GetLength(0);
xlen = mask.GetLength(1); 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]) 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); mapBounds = new Rectangle(0, 0, this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight);
//locations //locations
} }
var previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height); Single 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)); 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 (Bitmap fullBitmap = new Bitmap(this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight))
using (var croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height)) 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.SetRenderSettings(g, smooth);
MapRenderer.Render(gameType, this, g, locations, toRender, 1); MapRenderer.Render(gameType, this, g, locations, toRender, 1);
} }
using (var g = Graphics.FromImage(croppedBitmap)) using (Graphics g = Graphics.FromImage(croppedBitmap))
{ {
MapRenderer.SetRenderSettings(g, true); MapRenderer.SetRenderSettings(g, true);
Matrix transform = new Matrix(); Matrix transform = new Matrix();
@ -1419,7 +1423,7 @@ namespace MobiusEditor.Model
} }
if (sharpen) if (sharpen)
{ {
using (var sharpenedImage = croppedBitmap.Sharpen(1.0f)) using (Bitmap sharpenedImage = croppedBitmap.Sharpen(1.0f))
{ {
return TGA.FromBitmap(sharpenedImage); return TGA.FromBitmap(sharpenedImage);
} }
@ -1460,7 +1464,7 @@ namespace MobiusEditor.Model
{ {
return; return;
} }
foreach (var overlay in new Overlay[] { e.OldValue, e.Value }) foreach (Overlay overlay in new Overlay[] { e.OldValue, e.Value })
{ {
if (overlay == null) if (overlay == null)
{ {
@ -1736,7 +1740,7 @@ namespace MobiusEditor.Model
} }
} }
// Clean teamtypes // Clean teamtypes
foreach (var team in this.TeamTypes) foreach (TeamType team in this.TeamTypes)
{ {
String trig = team.Trigger; String trig = team.Trigger;
if (!Trigger.IsEmpty(trig) && !availableUnitTriggers.Contains(trig)) 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. // 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) if (trig == null)
{ {

View File

@ -48,8 +48,12 @@ namespace MobiusEditor.Model
{ {
return null; 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(); return map.TheaterTypes.Where(t => t.Equals(value)).FirstOrDefault() ?? map.TheaterTypes.First();
} }
} }

View File

@ -255,7 +255,6 @@ namespace MobiusEditor
// Buildings // Buildings
gtm["TEXT_STRUCTURE_TITLE_OIL_PUMP"] = "Oil Pump"; gtm["TEXT_STRUCTURE_TITLE_OIL_PUMP"] = "Oil Pump";
gtm["TEXT_STRUCTURE_TITLE_OIL_TANKER"] = "Oil Tanker"; gtm["TEXT_STRUCTURE_TITLE_OIL_TANKER"] = "Oil Tanker";
String fake = " (" + gtm["TEXT_UI_FAKE"] + ")"; 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_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; 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_WCRATE"] = "Wooden Crate";
gtm["TEXT_OVERLAY_SCRATE"] = "Steel Crate"; gtm["TEXT_OVERLAY_SCRATE"] = "Steel Crate";
gtm["TEXT_OVERLAY_WATER_CRATE"] = "Water Crate"; gtm["TEXT_OVERLAY_WATER_CRATE"] = "Water Crate";
// Smudge // Smudge
gtm["TEXT_SMUDGE_CRATER"] = "Crater"; gtm["TEXT_SMUDGE_CRATER"] = "Crater";
gtm["TEXT_SMUDGE_SCORCH"] = "Scorch Mark"; gtm["TEXT_SMUDGE_SCORCH"] = "Scorch Mark";

View File

@ -31,8 +31,8 @@ namespace MobiusEditor.RedAlert
public static readonly HouseType Turkey = new HouseType(7, "Turkey", "TURKEY"); public static readonly HouseType Turkey = new HouseType(7, "Turkey", "TURKEY");
public static readonly HouseType Good = new HouseType(8, "GoodGuy", "GOOD"); public static readonly HouseType Good = new HouseType(8, "GoodGuy", "GOOD");
public static readonly HouseType Bad = new HouseType(9, "BadGuy", "BAD"); public static readonly HouseType Bad = new HouseType(9, "BadGuy", "BAD");
public static readonly HouseType Neutral = new HouseType(10, "Neutral"); public static readonly HouseType Neutral = new HouseType(10, "Neutral", "NEUTRAL");
public static readonly HouseType Special = new HouseType(11, "Special", "SPAIN"); 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 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 Multi2 = new HouseType(13, "Multi2", WaypointFlag.PlayerStart2, "MULTI2"); // teal
public static readonly HouseType Multi3 = new HouseType(14, "Multi3", WaypointFlag.PlayerStart3, "MULTI3"); // red public static readonly HouseType Multi3 = new HouseType(14, "Multi3", WaypointFlag.PlayerStart3, "MULTI3"); // red

View File

@ -27,8 +27,8 @@ namespace MobiusEditor.TiberianDawn
public static readonly HouseType Good = new HouseType(0, "GoodGuy", "GOOD"); 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")); public static readonly HouseType Bad = new HouseType(1, "BadGuy", "BAD_UNIT", "BAD_STRUCTURE", ("harv", "BAD_STRUCTURE"), ("mcv", "BAD_STRUCTURE"));
// Added actual recoloring // Added actual recoloring
public static readonly HouseType Neutral = new HouseType(2, "Neutral", "GOOD"); public static readonly HouseType Neutral = new HouseType(2, "Neutral", "NEUTRAL");
public static readonly HouseType Special = new HouseType(3, "Special", "GOOD"); 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 // 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 // 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. // the multi-Houses in the Remastered game. The editor code logically assumed they were named after their House, and thus got it all wrong.

View File

@ -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>(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>(T[] arr1, T[] arr2) where T : IEquatable<T>
{
// 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;
}
/// <summary>
/// Creates and returns a new array, containing the contents of all the given arrays, in the given order.
/// </summary>
/// <typeparam name="T">Type of the arrays</typeparam>
/// <param name="arrays">Arrays to join together.</param>
/// <returns>A new array containing the contents of all given arrays, joined together.</returns>
public static T[] MergeArrays<T>(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>(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>(T obj) where T : struct
{
Endianness e = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian;
return ReadStructFromByteArray<T>(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<T>(Byte[] rawData, Endianness endianness) where T : struct
{
return ReadStructFromByteArray<T>(rawData, 0, endianness);
}
public static T ReadStructFromByteArray<T>(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>(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>(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
}
}

View File

@ -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);
}
/// <summary>
/// Retrieves the CPS image.
/// </summary>
/// <param name="fileData">Original file data.</param>
/// <param name="start">Start offset of the data.</param>
/// <returns>The raw 8-bit linear image data in a 64000 byte array.</returns>
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);
}
/// <summary>
/// Retrieves the CPS image.
/// </summary>
/// <param name="fileData">Original file data.</param>
/// <param name="start">Start offset of the data.</param>
/// <returns>The raw 8-bit linear image data in a 64000 byte array.</returns>
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;
}
}
}

View File

@ -402,7 +402,7 @@ namespace MobiusEditor.Utility
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length); Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
if (bitmap.PixelFormat == PixelFormat.Format8bppIndexed) 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 else
{ {
@ -501,7 +501,7 @@ namespace MobiusEditor.Utility
return opaqueBounds; return opaqueBounds;
} }
private static Rectangle CalculateOpaqueBounds8bpp(byte[] data, int width, int height, int stride, List<int> transparentColors) public static Rectangle CalculateOpaqueBounds8bpp(byte[] data, int width, int height, int stride, params int[] transparentColors)
{ {
HashSet<int> trMap = new HashSet<int>(transparentColors); HashSet<int> trMap = new HashSet<int>(transparentColors);
// Only handle 32bpp data. // Only handle 32bpp data.

View File

@ -13,6 +13,7 @@
// GNU General Public License along with permitted additional restrictions // GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface; using MobiusEditor.Interface;
using MobiusEditor.Model;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
@ -69,7 +70,7 @@ namespace MobiusEditor.Utility
this.megafileManager = megafileManager; this.megafileManager = megafileManager;
} }
public void Reset(GameType gameType, string theater) public void Reset(GameType gameType, TheaterType theater)
{ {
teamColors.Clear(); teamColors.Clear();
} }
@ -87,6 +88,7 @@ namespace MobiusEditor.Utility
} }
if (xmlDoc != null) if (xmlDoc != null)
{ {
teamColors.Clear();
foreach (XmlNode teamColorNode in xmlDoc.SelectNodes("/*/TeamColorTypeClass")) foreach (XmlNode teamColorNode in xmlDoc.SelectNodes("/*/TeamColorTypeClass"))
{ {
var teamColor = new TeamColor(this); var teamColor = new TeamColor(this);

View File

@ -2,7 +2,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,6 +18,15 @@ namespace MobiusEditor.Utility
private byte[] remapTable; 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) public TeamRemap(string name, byte unitRadarColor, byte buildingRadarColor, byte remapstart, byte[] remapValues)
{ {
this.Name = name; this.Name = name;
@ -42,15 +53,66 @@ namespace MobiusEditor.Utility
} }
} }
public void ApplyToImage(Bitmap image) public void ApplyToImage(Bitmap image)
{ {
throw new NotImplementedException(); ApplyToImage(image, out _);
} }
public void ApplyToImage(Bitmap image, out Rectangle opaqueBounds) 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;
}
} }
} }
} }

View File

@ -1,55 +1,74 @@
using MobiusEditor.Interface; using MobiusEditor.Interface;
using MobiusEditor.Model;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MobiusEditor.Utility namespace MobiusEditor.Utility
{ {
class TeamRemapManager : ITeamColorManager class TeamRemapManager : ITeamColorManager
{ {
// TD remap // 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 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 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 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 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 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. // 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 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 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 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 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 }); 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 // 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 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 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 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 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 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<string, TeamRemap> RemapsTd; private static readonly Dictionary<string, TeamRemap> RemapsTd;
public const Byte MaxValueSixBit = 63;
private static readonly Byte[] ConvertToEightBit = new Byte[64];
static TeamRemapManager() static TeamRemapManager()
{ {
RemapsTd = (from field in typeof(ITeamColorManager).GetFields(BindingFlags.Static | BindingFlags.Public) RemapsTd = (from field in typeof(ITeamColorManager).GetFields(BindingFlags.Static | BindingFlags.Public)
where field.IsInitOnly && typeof(TeamRemap).IsAssignableFrom(field.FieldType) && field.Name.StartsWith("RemapTd") where field.IsInitOnly && typeof(TeamRemap).IsAssignableFrom(field.FieldType) && field.Name.StartsWith("RemapTd")
select field.GetValue(null) as TeamRemap).ToDictionary(trm => trm.Name); select field.GetValue(null) as TeamRemap).ToDictionary(trm => trm.Name);
// Build easy lookup tables for this, so no calculations are ever needed for this later.
for (Int32 i = 0; i < 64; ++i)
ConvertToEightBit[i] = (Byte)Math.Round(i * 255.0 / 63.0, MidpointRounding.ToEven);
} }
private Dictionary<string, TeamRemap> remapsRa = new Dictionary<string, TeamRemap>(); private Dictionary<string, TeamRemap> remapsRa = new Dictionary<string, TeamRemap>();
private GameType currentlyLoadedGameType; private GameType currentlyLoadedGameType;
private Color[] currentlyLoadedPalette; private Color[] currentlyLoadedPalette;
private readonly IArchiveManager mixfileManager; private readonly IArchiveManager mixfileManager;
private readonly string[] remapsColorsRa =
{
"GOLD",
"LTBLUE",
"RED",
"GREEN",
"ORANGE",
"GREY",
"BLUE",
"BROWN",
//"TYPE",
//"REALLY_BLUE",
//"DIALOG_BLUE",
};
private readonly Dictionary<string, string[]> remapUseRa = new Dictionary<string, string[]> {
{ "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 ITeamColor this[string key] => GetforCurrentGame(key);
public Color GetBaseColor(string key) public Color GetBaseColor(string key)
@ -89,7 +108,64 @@ namespace MobiusEditor.Utility
public void Load(string path) 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<string, TeamRemap> raRemapColors = new Dictionary<string, TeamRemap>();
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) public TeamRemapManager(IArchiveManager fileManager)
@ -97,41 +173,32 @@ namespace MobiusEditor.Utility
this.mixfileManager = fileManager; this.mixfileManager = fileManager;
} }
public void Reset(GameType gameType, string theater) public void Reset(GameType gameType, TheaterType theater)
{ {
this.remapsRa.Clear();
currentlyLoadedGameType = gameType; currentlyLoadedGameType = gameType;
currentlyLoadedPalette = new Color[0x100];
// file manager should already be reset to read from the correct game at this point. // 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; byte[] pal;
using (BinaryReader sr = new BinaryReader(palette)) using (BinaryReader sr = new BinaryReader(palette))
{ {
pal = GeneralUtils.ReadAllBytes(sr); pal = GeneralUtils.ReadAllBytes(sr);
} }
int len = Math.Min(pal.Length / 3, 0x100); currentlyLoadedPalette = ClassicSpriteLoader.ReadSixBitPaletteAsEightBit(pal, 0, 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;
} }
} }
} // Set background transparent
if (gameType == GameType.TiberianDawn || gameType == GameType.SoleSurvivor) 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);
}
}
public void setTheater()
{
} }
} }
} }

View File

@ -24,6 +24,7 @@ using System.IO.Compression;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using TGASharpLib; using TGASharpLib;
using MobiusEditor.Interface; using MobiusEditor.Interface;
using MobiusEditor.Model;
namespace MobiusEditor.Utility namespace MobiusEditor.Utility
{ {
@ -42,7 +43,7 @@ namespace MobiusEditor.Utility
this.megafileManager = megafileManager; this.megafileManager = megafileManager;
} }
public void Reset(GameType gameType, string theater) public void Reset(GameType gameType, TheaterType theater)
{ {
Bitmap[] cachedImages = cachedTextures.Values.ToArray(); Bitmap[] cachedImages = cachedTextures.Values.ToArray();
cachedTextures.Clear(); cachedTextures.Clear();