* Fixed gem value calculation
* Added all code for classic color handling
This commit is contained in:
parent
303762eb31
commit
58be2ebd90
@ -583,6 +583,7 @@
|
||||
<Compile Include="Tools\Dialogs\WaypointsToolDialog.Designer.cs">
|
||||
<DependentUpon>WaypointsToolDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Utility\ArrayUtils.cs" />
|
||||
<Compile Include="Utility\BlobDetection.cs" />
|
||||
<Compile Include="Tools\InfantryTool.cs" />
|
||||
<Compile Include="Tools\OverlaysTool.cs" />
|
||||
@ -596,6 +597,7 @@
|
||||
<Compile Include="Tools\CellTriggersTool.cs" />
|
||||
<Compile Include="Tools\WaypointsTool.cs" />
|
||||
<Compile Include="Utility\BooleanStringStyle.cs" />
|
||||
<Compile Include="Utility\ClassicSpriteLoader.cs" />
|
||||
<Compile Include="Utility\CRC.cs" />
|
||||
<Compile Include="Utility\ExtensionMethods.cs" />
|
||||
<Compile Include="Utility\GameTextManager.cs" />
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The separate-threaded part for making a new map.
|
||||
/// </summary>
|
||||
|
@ -472,7 +472,7 @@ namespace MobiusEditor.Model
|
||||
this.Overlappers = new OverlapperSet<ICellOverlapper>(this.Metrics);
|
||||
this.triggers = new List<Trigger>();
|
||||
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.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<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 eastWall = this.Overlay.Adjacent(location, FacingType.East);
|
||||
@ -714,18 +713,18 @@ namespace MobiusEditor.Model
|
||||
|
||||
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[] 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<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.
|
||||
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<ITechno> 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)
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
374
CnCTDRAMapEditor/Utility/ArrayUtils.cs
Normal file
374
CnCTDRAMapEditor/Utility/ArrayUtils.cs
Normal 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
|
||||
}
|
||||
}
|
122
CnCTDRAMapEditor/Utility/ClassicSpriteLoader.cs
Normal file
122
CnCTDRAMapEditor/Utility/ClassicSpriteLoader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<int> transparentColors)
|
||||
public static Rectangle CalculateOpaqueBounds8bpp(byte[] data, int width, int height, int stride, params int[] transparentColors)
|
||||
{
|
||||
HashSet<int> trMap = new HashSet<int>(transparentColors);
|
||||
// Only handle 32bpp data.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<string, TeamRemap> 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<string, TeamRemap> remapsRa = new Dictionary<string, TeamRemap>();
|
||||
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<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 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<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)
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user