* Added rules reading for RA, including terrain type data.
* The remastered and classic mode now call the same function for loading classic RA mix data. * Added "impassable water" indicator.
This commit is contained in:
parent
25425f6833
commit
f6e6564ac9
@ -466,20 +466,20 @@ Feature updates:
|
||||
* Added checks on the validation of special waypoints to make sure they are actually inside the map bounds.
|
||||
* Added a warning when RA ant units or structures are used in the map, but no rule definitions for them exist in the ini.
|
||||
* Added an option in the trigger filter dialog to filter on triggers. This will filter out the trigger itself, and any triggers destroying or forcing the selected trigger.
|
||||
* When an RA trigger is set to type E1->A1, E2->A2, the controls for the events and actions will be reordered to accurately represent this.
|
||||
* When an RA trigger is set to type E1→A1, E2→A2, the controls for the events and actions will be reordered to accurately represent this.
|
||||
* Added zoom options to the View menu.
|
||||
* Added F-keys as shortcuts for the "Extra Indicator" options in the View menu .
|
||||
* Added F-keys as shortcuts for the "Extra Indicator" options in the View menu.
|
||||
* The user can now place map tiles partially outside the map at the top and left side.
|
||||
* Teamtypes now show full unit names.
|
||||
* The argument dropdown for "Built It" triggers now shows the available theaters on theater-specific buildings in the list.
|
||||
* Tile randomising now avoids identical adjacent tiles.
|
||||
* Units, buildings and waypoints with a radius will now show that radius more clearly in placement preview mode.
|
||||
* Red Alert data concerning rules.ini data, and map tileset data (dimensions, tile usage, land types) is now read from the original classic files.
|
||||
|
||||
Map logic updates:
|
||||
|
||||
* All overlay placement is now correctly restricted to not be allowed on the top or bottom row of the map, showing red indicators when in placement mode.
|
||||
* Resource placement with a brush size larger than 1 shows red cells inside the brush area when hovering over the top or bottom cells of the map. At size 1, the brush is simply completely red.
|
||||
* The automatic tiling of clear terrain used a logic that was incorrect for the larger maps in Red Alert and Sole Survivor. This has now been fixed.
|
||||
|
||||
Program bug fixes:
|
||||
|
||||
@ -514,3 +514,4 @@ Program bug fixes:
|
||||
* Added map load checks on failing to detect the House of units, structures, triggers and teams. This includes a logic for RA to substitute the prerelease House Italy with its final version, Ukraine.
|
||||
* Added relevant theaters to theater-specific buildings in the "Built it" trigger event lists.
|
||||
* Fixed bug where the radius painting of the placement preview for gap generators wouldn't work correctly because it could get mixed up with buildings set to be built later.
|
||||
* The automatic tiling of clear terrain used a logic that was incorrect for the larger maps in Red Alert and Sole Survivor. This has now been fixed.
|
||||
|
@ -157,8 +157,8 @@ namespace MobiusEditor.Interface
|
||||
/// <returns></returns>
|
||||
ITeamColor[] GetFlagColors();
|
||||
|
||||
bool IsVehiclePassable(LandType landType);
|
||||
bool IsBuildable(LandType landType);
|
||||
bool IsLandUnitPassable(LandType landType);
|
||||
bool IsBoatPassable(LandType landType);
|
||||
bool IsBuildable(LandType landType);
|
||||
}
|
||||
}
|
||||
|
@ -611,7 +611,8 @@ namespace MobiusEditor
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool expansionEnabled = plugin.Map.BasicSection.ExpansionEnabled;
|
||||
bool wasSolo = plugin.Map.BasicSection.SoloMission;
|
||||
bool wasExpanded = plugin.Map.BasicSection.ExpansionEnabled;
|
||||
bool rulesChanged = false;
|
||||
PropertyTracker<BasicSection> basicSettings = new PropertyTracker<BasicSection>(plugin.Map.BasicSection);
|
||||
PropertyTracker<BriefingSection> briefingSettings = new PropertyTracker<BriefingSection>(plugin.Map.BriefingSection);
|
||||
@ -651,7 +652,12 @@ namespace MobiusEditor
|
||||
// Ignore trivial line changes. This will not detect any irrelevant but non-trivial changes like swapping lines, though.
|
||||
String checkTextNew = Regex.Replace(normalised, "[\\r\\n]+", "\n").Trim('\n');
|
||||
String checkTextOrig = Regex.Replace(extraIniText ?? String.Empty, "[\\r\\n]+", "\n").Trim('\n');
|
||||
if (!checkTextOrig.Equals(checkTextNew, StringComparison.OrdinalIgnoreCase))
|
||||
bool amStatusChanged = wasExpanded != plugin.Map.BasicSection.ExpansionEnabled;
|
||||
bool multiStatusChanged = wasSolo != plugin.Map.BasicSection.SoloMission;
|
||||
bool iniTextChanged = !checkTextOrig.Equals(checkTextNew, StringComparison.OrdinalIgnoreCase);
|
||||
// All three of those warrant a rules reset.
|
||||
// TODO: give warning on the multiplay rules changes.
|
||||
if (amStatusChanged || multiStatusChanged || iniTextChanged)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -669,13 +675,14 @@ namespace MobiusEditor
|
||||
emb.ShowDialog(this);
|
||||
}
|
||||
}
|
||||
// Maybe make more advanced logic to check if any bibs changed, and don't clear if not needed?
|
||||
rulesChanged = plugin.GameType == GameType.RedAlert;
|
||||
hasChanges = true;
|
||||
}
|
||||
plugin.Dirty = hasChanges;
|
||||
}
|
||||
}
|
||||
if (rulesChanged || (expansionEnabled && !plugin.Map.BasicSection.ExpansionEnabled))
|
||||
if (rulesChanged || (wasExpanded && !plugin.Map.BasicSection.ExpansionEnabled))
|
||||
{
|
||||
// If Aftermath units were disabled, we can't guarantee none of them are still in
|
||||
// the undo/redo history, so the undo/redo history is cleared to avoid issues.
|
||||
@ -1107,7 +1114,7 @@ namespace MobiusEditor
|
||||
{
|
||||
var fileInfo = new FileInfo(fileName);
|
||||
String name = fileInfo.FullName;
|
||||
if (!IdentifyMap(name, out FileType fileType, out GameType gameType, out bool isTdMegaMap, out string theater))
|
||||
if (!IdentifyMap(name, out FileType fileType, out GameType gameType, out bool isMegaMap, out string theater))
|
||||
{
|
||||
string extension = Path.GetExtension(name).TrimStart('.');
|
||||
// No point in supporting jpeg here; the mapping needs distinct colours without fades.
|
||||
@ -1135,7 +1142,7 @@ namespace MobiusEditor
|
||||
return;
|
||||
}
|
||||
loadMultiThreader.ExecuteThreaded(
|
||||
() => LoadFile(name, fileType, gameType, theater, isTdMegaMap),
|
||||
() => LoadFile(name, fileType, gameType, theater, isMegaMap),
|
||||
PostLoad, true,
|
||||
(e,l) => LoadUnloadUi(e, l, loadMultiThreader),
|
||||
"Loading map");
|
||||
@ -1167,6 +1174,10 @@ namespace MobiusEditor
|
||||
fileType = FileType.INI;
|
||||
}
|
||||
}
|
||||
if (plugin.MapNameIsEmpty(plugin.Map.BasicSection.Name))
|
||||
{
|
||||
plugin.Map.BasicSection.Name = Path.GetFileNameWithoutExtension(saveFilename);
|
||||
}
|
||||
// Once saved, leave it to be manually handled on steam publish.
|
||||
if (string.IsNullOrEmpty(plugin.Map.SteamSection.Title) || plugin.Map.SteamSection.PublishedFileId == 0)
|
||||
{
|
||||
@ -1181,12 +1192,12 @@ namespace MobiusEditor
|
||||
"Saving map");
|
||||
}
|
||||
|
||||
private Boolean IdentifyMap(String loadFilename, out FileType fileType, out GameType gameType, out bool isTdMegaMap, out string theater)
|
||||
private Boolean IdentifyMap(String loadFilename, out FileType fileType, out GameType gameType, out bool isMegaMap, out string theater)
|
||||
{
|
||||
fileType = FileType.None;
|
||||
gameType = GameType.None;
|
||||
theater = null;
|
||||
isTdMegaMap = false;
|
||||
isMegaMap = false;
|
||||
try
|
||||
{
|
||||
if (!File.Exists(loadFilename))
|
||||
@ -1294,12 +1305,17 @@ namespace MobiusEditor
|
||||
}
|
||||
if (gameType == GameType.TiberianDawn)
|
||||
{
|
||||
isTdMegaMap = TiberianDawn.GamePluginTD.CheckForMegamap(iniContents);
|
||||
isMegaMap = TiberianDawn.GamePluginTD.CheckForMegamap(iniContents);
|
||||
if (SoleSurvivor.GamePluginSS.CheckForSSmap(iniContents))
|
||||
{
|
||||
gameType = GameType.SoleSurvivor;
|
||||
}
|
||||
}
|
||||
else if (gameType == GameType.RedAlert)
|
||||
{
|
||||
// Not actually used for RA at the moment.
|
||||
isMegaMap = true;
|
||||
}
|
||||
return gameType != GameType.None;
|
||||
}
|
||||
|
||||
@ -1353,28 +1369,28 @@ namespace MobiusEditor
|
||||
}
|
||||
}
|
||||
|
||||
private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap)
|
||||
private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isMegaMap)
|
||||
{
|
||||
return LoadNewPlugin(gameType, theater, isTdMegaMap, false);
|
||||
return LoadNewPlugin(gameType, theater, isMegaMap, false);
|
||||
}
|
||||
|
||||
private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, bool noImage)
|
||||
private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isMegaMap, bool noImage)
|
||||
{
|
||||
// Get plugin type
|
||||
IGamePlugin plugin = null;
|
||||
RedAlert.GamePluginRA raPlugin = null;
|
||||
if (gameType == GameType.TiberianDawn)
|
||||
{
|
||||
plugin = new TiberianDawn.GamePluginTD(!noImage, isTdMegaMap);
|
||||
plugin = new TiberianDawn.GamePluginTD(!noImage, isMegaMap);
|
||||
}
|
||||
else if (gameType == GameType.RedAlert)
|
||||
{
|
||||
raPlugin = new RedAlert.GamePluginRA(!noImage);
|
||||
raPlugin = new RedAlert.GamePluginRA(!noImage); // isMegaMap);
|
||||
plugin = raPlugin;
|
||||
}
|
||||
else if (gameType == GameType.SoleSurvivor)
|
||||
{
|
||||
plugin = new SoleSurvivor.GamePluginSS(!noImage, isTdMegaMap);
|
||||
plugin = new SoleSurvivor.GamePluginSS(!noImage, isMegaMap);
|
||||
}
|
||||
// Get theater object
|
||||
TheaterTypeConverter ttc = new TheaterTypeConverter();
|
||||
@ -1390,15 +1406,15 @@ namespace MobiusEditor
|
||||
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
|
||||
AddTeamColorsTD(Globals.TheTeamColorManager);
|
||||
}
|
||||
else if (gameType == GameType.RedAlert)
|
||||
else if (gameType == GameType.RedAlert && raPlugin != null)
|
||||
{
|
||||
if (raPlugin != null)
|
||||
{
|
||||
Byte[] rulesFile = Globals.TheArchiveManager.ReadFileClassic("rules.ini");
|
||||
Byte[] rulesUpdFile = Globals.TheArchiveManager.ReadFileClassic("aftrmath.ini");
|
||||
raPlugin.ReadRules(rulesFile);
|
||||
raPlugin.PatchRules(rulesUpdFile);
|
||||
}
|
||||
Byte[] rulesFile = Globals.TheArchiveManager.ReadFileClassic("rules.ini");
|
||||
Byte[] rulesUpdFile = Globals.TheArchiveManager.ReadFileClassic("aftrmath.ini");
|
||||
Byte[] rulesMpFile = Globals.TheArchiveManager.ReadFileClassic("mplayer.ini");
|
||||
// This returns errors in original rules files. Ignore for now.
|
||||
raPlugin.ReadRules(rulesFile);
|
||||
raPlugin.ReadExpandRules(rulesUpdFile);
|
||||
raPlugin.ReadMultiRules(rulesMpFile);
|
||||
// Only one will be found.
|
||||
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML");
|
||||
Globals.TheTeamColorManager.Load("palette.cps");
|
||||
@ -1409,7 +1425,7 @@ namespace MobiusEditor
|
||||
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
|
||||
AddTeamColorsTD(Globals.TheTeamColorManager);
|
||||
}
|
||||
// Needs to be done after the whole init.
|
||||
// Needs to be done after the whole init, so colors reading is properly initialised.
|
||||
plugin.Map.FlagColors = plugin.GetFlagColors();
|
||||
return plugin;
|
||||
}
|
||||
@ -1560,18 +1576,18 @@ namespace MobiusEditor
|
||||
/// <summary>
|
||||
/// The separate-threaded part for loading a map.
|
||||
/// </summary>
|
||||
/// <param name="loadFilename"></param>
|
||||
/// <param name="fileType"></param>
|
||||
/// <param name="gameType"></param>
|
||||
/// <param name="isTdMegaMap"></param>
|
||||
/// <param name="loadFilename">File to load.</param>
|
||||
/// <param name="fileType">Type of the loaded file (detected in advance).</param>
|
||||
/// <param name="gameType">Game type (detected in advance)</param>
|
||||
/// <param name="isMegaMap">True if this is a megamap.</param>
|
||||
/// <returns></returns>
|
||||
private static MapLoadInfo LoadFile(string loadFilename, FileType fileType, GameType gameType, string theater, bool isTdMegaMap)
|
||||
private static MapLoadInfo LoadFile(string loadFilename, FileType fileType, GameType gameType, string theater, bool isMegaMap)
|
||||
{
|
||||
IGamePlugin plugin = null;
|
||||
bool mapLoaded = false;
|
||||
try
|
||||
{
|
||||
plugin = LoadNewPlugin(gameType, theater, isTdMegaMap);
|
||||
plugin = LoadNewPlugin(gameType, theater, isMegaMap);
|
||||
string[] errors = plugin.Load(loadFilename, fileType).ToArray();
|
||||
mapLoaded = true;
|
||||
return new MapLoadInfo(loadFilename, fileType, plugin, errors, true);
|
||||
|
@ -65,9 +65,10 @@ namespace MobiusEditor.Model
|
||||
}
|
||||
str = str.Trim();
|
||||
var mapContext = context as MapContext;
|
||||
if (str.EndsWith("%"))
|
||||
bool isPercentage = str.EndsWith("%");
|
||||
if (isPercentage)
|
||||
str = str.Substring(0, str.Length - 1);
|
||||
if (mapContext != null && mapContext.FractionalPercentages && str.Contains("."))
|
||||
if (mapContext != null && mapContext.FractionalPercentages && !isPercentage && str.TrimStart('0').StartsWith("."))
|
||||
{
|
||||
if (!decimal.TryParse(str, out decimal percent))
|
||||
{
|
||||
|
@ -166,10 +166,54 @@ namespace MobiusEditor
|
||||
gameFolders.Add(GameType.TiberianDawn, tdPath);
|
||||
gameFolders.Add(GameType.RedAlert, raPath);
|
||||
gameFolders.Add(GameType.SoleSurvivor, ssPath);
|
||||
// Check files
|
||||
modpaths.TryGetValue(GameType.TiberianDawn, out string[] tdModPaths);
|
||||
modpaths.TryGetValue(GameType.SoleSurvivor, out string[] ssModPaths);
|
||||
bool tdSsEqual = ssModPaths.SequenceEqual(tdModPaths) && tdPathFull.Equals(ssPathFull);
|
||||
MixfileManager mfm = new MixfileManager(ApplicationPath, gameFolders, modpaths);
|
||||
Globals.TheArchiveManager = mfm;
|
||||
List<string> loadErrors = new List<string>();
|
||||
List<string> fileLoadErrors = new List<string>();
|
||||
InitClassicFilesTdSs(mfm, tdSsEqual, loadErrors, fileLoadErrors);
|
||||
InitClassicFilesRa(mfm, loadErrors, fileLoadErrors, false);
|
||||
#if !DEVELOPER
|
||||
if (loadErrors.Count > 0)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("Required data is missing or corrupt. The following mix files could not be opened:").Append('\n');
|
||||
string errors = String.Join("\n", loadErrors.ToArray());
|
||||
msg.Append(errors);
|
||||
MessageBox.Show(msg.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
if (fileLoadErrors.Count > 0)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("Required data is missing or corrupt. The following data files could not be opened:").Append('\n');
|
||||
string errors = String.Join("\n", fileLoadErrors.ToArray());
|
||||
msg.Append(errors);
|
||||
MessageBox.Show(msg.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
Globals.TheArchiveManager = mfm;
|
||||
// Initialize texture, tileset, team color, and game text managers
|
||||
// TilesetManager: is the system graphics are requested from, possibly with house remap.
|
||||
Globals.TheTilesetManager = new TilesetManagerClassic(mfm);
|
||||
|
||||
Globals.TheTeamColorManager = new TeamRemapManager(mfm);
|
||||
// All the same. Would introduce region-based language differences, but the French and German files are... also called "conquer.eng".
|
||||
Dictionary<GameType, String> gameStringsFiles = new Dictionary<GameType, string>();
|
||||
gameStringsFiles.Add(GameType.TiberianDawn, "conquer.eng");
|
||||
gameStringsFiles.Add(GameType.RedAlert, "conquer.eng");
|
||||
gameStringsFiles.Add(GameType.SoleSurvivor, "conquer.eng");
|
||||
GameTextManagerClassic gtm = new GameTextManagerClassic(mfm, gameStringsFiles);
|
||||
AddMissingClassicText(gtm);
|
||||
Globals.TheGameTextManager = gtm;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void InitClassicFilesTdSs(MixfileManager mfm, bool tdSsEqual, List<string> loadErrors, List<string> fileLoadErrors)
|
||||
{
|
||||
// This will map the mix files to the respective games, and look for them in the respective folders.
|
||||
// Tiberian Dawn
|
||||
mfm.LoadArchive(GameType.TiberianDawn, "local.mix", false);
|
||||
@ -181,10 +225,6 @@ namespace MobiusEditor
|
||||
mfm.LoadArchive(GameType.TiberianDawn, "desert.mix", true);
|
||||
mfm.LoadArchive(GameType.TiberianDawn, "temperat.mix", true);
|
||||
mfm.LoadArchive(GameType.TiberianDawn, "winter.mix", true);
|
||||
// Check files
|
||||
modpaths.TryGetValue(GameType.TiberianDawn, out string[] tdModPaths);
|
||||
modpaths.TryGetValue(GameType.SoleSurvivor, out string[] ssModPaths);
|
||||
bool tdSsEqual = ssModPaths.SequenceEqual(tdModPaths) && tdPathFull.Equals(ssPathFull);
|
||||
mfm.Reset(GameType.TiberianDawn, null);
|
||||
List<String> loadedFiles = mfm.ToList();
|
||||
string prefix = tdSsEqual ? "TD/SS: " : "TD: ";
|
||||
@ -218,6 +258,11 @@ namespace MobiusEditor
|
||||
if (!loadedFiles.Contains("winter.mix")) loadErrors.Add(prefix + "winter.mix");
|
||||
if (!mfm.FileExists("conquer.eng")) fileLoadErrors.Add(prefix + "conquer.eng");
|
||||
}
|
||||
mfm.Reset(GameType.None, null);
|
||||
}
|
||||
|
||||
private static void InitClassicFilesRa(MixfileManager mfm, List<string> loadErrors, List<string> fileLoadErrors, bool forRemaster)
|
||||
{
|
||||
// Red Alert
|
||||
// Aftermath expand file. Required. Contains latest strings file.
|
||||
mfm.LoadArchive(GameType.RedAlert, "expand2.mix", false, false, false, true);
|
||||
@ -229,10 +274,10 @@ namespace MobiusEditor
|
||||
mfm.LoadArchive(GameType.RedAlert, "main.mix", false, true, false, true);
|
||||
// Needed for theater palettes and the remap settings in palette.cps
|
||||
mfm.LoadArchive(GameType.RedAlert, "local.mix", false, false, true, true);
|
||||
// Not normally needed, but in the beta this contains palette.cps.
|
||||
mfm.LoadArchive(GameType.RedAlert, "general.mix", false, false, true, true);
|
||||
// Mod addons
|
||||
mfm.LoadArchives(GameType.RedAlert, "sc*.mix", true);
|
||||
// Not normally needed, but in the beta this contains palette.cps.
|
||||
mfm.LoadArchive(GameType.RedAlert, "general.mix", false, false, true, true);
|
||||
// Main graphics archive
|
||||
mfm.LoadArchive(GameType.RedAlert, "conquer.mix", false, false, true, true);
|
||||
// Infantry
|
||||
@ -245,56 +290,29 @@ namespace MobiusEditor
|
||||
mfm.LoadArchive(GameType.RedAlert, "interior.mix", true, false, true, true);
|
||||
|
||||
// Check files
|
||||
modpaths.TryGetValue(GameType.RedAlert, out string[] raModPaths);
|
||||
mfm.Reset(GameType.RedAlert, null);
|
||||
loadedFiles = mfm.ToList();
|
||||
prefix = "RA: ";
|
||||
List<String> loadedFiles = mfm.ToList();
|
||||
String prefix = "RA: ";
|
||||
if (!loadedFiles.Contains("expand2.mix")) loadErrors.Add(prefix + "expand2.mix");
|
||||
if (!loadedFiles.Contains("local.mix")) loadErrors.Add(prefix + "local.mix");
|
||||
if (!loadedFiles.Contains("conquer.mix")) loadErrors.Add(prefix + "conquer.mix");
|
||||
if (!loadedFiles.Contains("lores.mix")) loadErrors.Add(prefix + "lores.mix");
|
||||
if (!loadedFiles.Contains("lores1.mix")) loadErrors.Add(prefix + "lores1.mix");
|
||||
if (!forRemaster)
|
||||
{
|
||||
if (!loadedFiles.Contains("conquer.mix")) loadErrors.Add(prefix + "conquer.mix");
|
||||
if (!loadedFiles.Contains("lores.mix")) loadErrors.Add(prefix + "lores.mix");
|
||||
if (!loadedFiles.Contains("lores1.mix")) loadErrors.Add(prefix + "lores1.mix");
|
||||
}
|
||||
if (!loadedFiles.Contains("temperat.mix")) loadErrors.Add(prefix + "temperat.mix");
|
||||
if (!loadedFiles.Contains("snow.mix")) loadErrors.Add(prefix + "snow.mix");
|
||||
if (!loadedFiles.Contains("interior.mix")) loadErrors.Add(prefix + "interior.mix");
|
||||
if (!mfm.FileExists("palette.cps")) fileLoadErrors.Add(prefix + "palette.cps");
|
||||
if (!mfm.FileExists("conquer.eng")) fileLoadErrors.Add(prefix + "conquer.eng");
|
||||
if (!forRemaster)
|
||||
{
|
||||
if (!mfm.FileExists("palette.cps")) fileLoadErrors.Add(prefix + "palette.cps");
|
||||
if (!mfm.FileExists("conquer.eng")) fileLoadErrors.Add(prefix + "conquer.eng");
|
||||
}
|
||||
if (!mfm.FileExists("rules.ini")) fileLoadErrors.Add(prefix + "rules.ini");
|
||||
if (!mfm.FileExists("aftrmath.ini")) fileLoadErrors.Add(prefix + "aftrmath.ini");
|
||||
if (!mfm.FileExists("mplayer.ini")) fileLoadErrors.Add(prefix + "mplayer.ini");
|
||||
mfm.Reset(GameType.None, null);
|
||||
|
||||
#if !DEVELOPER
|
||||
if (loadErrors.Count > 0)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("Required data is missing or corrupt. The following mix files could not be opened:").Append('\n');
|
||||
string errors = String.Join("\n", loadErrors.ToArray());
|
||||
msg.Append(errors);
|
||||
MessageBox.Show(msg.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
if (fileLoadErrors.Count > 0)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("Required data is missing or corrupt. The following data files could not be opened:").Append('\n');
|
||||
string errors = String.Join("\n", fileLoadErrors.ToArray());
|
||||
msg.Append(errors);
|
||||
MessageBox.Show(msg.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// Initialize texture, tileset, team color, and game text managers
|
||||
// TilesetManager: is the system graphics are requested from, possibly with house remap.
|
||||
Globals.TheTilesetManager = new TilesetManagerClassic(mfm);
|
||||
|
||||
Globals.TheTeamColorManager = new TeamRemapManager(mfm);
|
||||
// All the same. Would introduce region-based language differences, but the French and German files are... also called "conquer.eng".
|
||||
Dictionary<GameType, String> gameStringsFiles = new Dictionary<GameType, string>();
|
||||
gameStringsFiles.Add(GameType.TiberianDawn, "conquer.eng");
|
||||
gameStringsFiles.Add(GameType.RedAlert, "conquer.eng");
|
||||
gameStringsFiles.Add(GameType.SoleSurvivor, "conquer.eng");
|
||||
GameTextManagerClassic gtm = new GameTextManagerClassic(mfm, gameStringsFiles);
|
||||
AddMissingClassicText(gtm);
|
||||
Globals.TheGameTextManager = gtm;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool LoadEditorRemastered(String runPath, Dictionary<GameType, string[]> modPaths)
|
||||
@ -312,17 +330,36 @@ namespace MobiusEditor
|
||||
megafilesLoaded &= mfm.LoadArchive("TEXTURES_RA_SRGB.MEG");
|
||||
megafilesLoaded &= mfm.LoadArchive("TEXTURES_SRGB.MEG");
|
||||
megafilesLoaded &= mfm.LoadArchive("TEXTURES_TD_SRGB.MEG");
|
||||
// Classic main.mix and theater files, for template land type detection in RA.
|
||||
mfm.LoadArchiveClassic(GameType.RedAlert, "main.mix", false, true, false, true);
|
||||
mfm.LoadArchiveClassic(GameType.RedAlert, "temperat.mix", true, false, true, true);
|
||||
mfm.LoadArchiveClassic(GameType.RedAlert, "snow.mix", true, false, true, true);
|
||||
mfm.LoadArchiveClassic(GameType.RedAlert, "interior.mix", true, false, true, true);
|
||||
#if !DEVELOPER
|
||||
if (!megafilesLoaded)
|
||||
{
|
||||
MessageBox.Show("Required data is missing or corrupt, please validate your installation.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// Classic main.mix and theater files, for rules reading and template land type detection in RA.
|
||||
List<string> loadErrors = new List<string>();
|
||||
List<string> fileLoadErrors = new List<string>();
|
||||
InitClassicFilesRa(mfm.ClassicFileManager, loadErrors, fileLoadErrors, true);
|
||||
#if !DEVELOPER
|
||||
if (loadErrors.Count > 0)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("Required classic data is missing or corrupt. The following mix files could not be opened:").Append('\n');
|
||||
string errors = String.Join("\n", loadErrors.ToArray());
|
||||
msg.Append(errors);
|
||||
MessageBox.Show(msg.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
if (fileLoadErrors.Count > 0)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("Required classic data is missing or corrupt. The following data files could not be opened:").Append('\n');
|
||||
string errors = String.Join("\n", fileLoadErrors.ToArray());
|
||||
msg.Append(errors);
|
||||
MessageBox.Show(msg.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
Globals.TheArchiveManager = mfm;
|
||||
// Initialize texture, tileset, team color, and game text managers
|
||||
|
@ -298,56 +298,26 @@ namespace MobiusEditor.RedAlert
|
||||
{
|
||||
get
|
||||
{
|
||||
INI ini = new INI();
|
||||
INI extraTextIni = new INI();
|
||||
if (extraSections != null)
|
||||
{
|
||||
ini.Sections.AddRange(extraSections);
|
||||
extraTextIni.Sections.AddRange(extraSections);
|
||||
}
|
||||
return ini.ToString();
|
||||
return extraTextIni.ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
INI ini = new INI();
|
||||
INI extraTextIni = new INI();
|
||||
try
|
||||
{
|
||||
ini.Parse(value);
|
||||
extraTextIni.Parse(value ?? String.Empty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Strip "NewUnitsEnabled" from the Aftermath section.
|
||||
INISection amSection = ini.Sections["Aftermath"];
|
||||
if (amSection != null)
|
||||
{
|
||||
amSection.Keys.Remove("NewUnitsEnabled");
|
||||
}
|
||||
// Remove any sections known and handled / disallowed by the editor.
|
||||
ini.Sections.Remove("Digest");
|
||||
INITools.ClearDataFrom(ini, "Basic", (BasicSection)Map.BasicSection);
|
||||
INITools.ClearDataFrom(ini, "Map", Map.MapSection);
|
||||
ini.Sections.Remove("Steam");
|
||||
ini.Sections.Remove("TeamTypes");
|
||||
ini.Sections.Remove("Trigs");
|
||||
ini.Sections.Remove("MapPack");
|
||||
ini.Sections.Remove("Terrain");
|
||||
ini.Sections.Remove("OverlayPack");
|
||||
ini.Sections.Remove("Smudge");
|
||||
ini.Sections.Remove("Units");
|
||||
ini.Sections.Remove("Aircraft");
|
||||
ini.Sections.Remove("Ships");
|
||||
ini.Sections.Remove("Infantry");
|
||||
ini.Sections.Remove("Structures");
|
||||
ini.Sections.Remove("Base");
|
||||
ini.Sections.Remove("Waypoints");
|
||||
ini.Sections.Remove("CellTriggers");
|
||||
ini.Sections.Remove("Briefing");
|
||||
foreach (House house in Map.Houses)
|
||||
{
|
||||
INITools.ClearDataFrom(ini, house.Type.Name, house);
|
||||
}
|
||||
extraSections = ini.Sections.Count == 0 ? null : ini.Sections;
|
||||
IEnumerable<string> errors = UpdateRules(ini, this.Map);
|
||||
IEnumerable<string> errors = ResetRules(extraTextIni);
|
||||
extraSections = extraTextIni.Sections.Count == 0 ? null : extraTextIni.Sections;
|
||||
if (errors.Count() > 0)
|
||||
{
|
||||
// Kind of a weird case; rules text is indeed updated, but this is the only way to give feedback.
|
||||
@ -356,6 +326,75 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trims the given extra ini content to just unmanaged information,
|
||||
/// resets the plugin's rules to their defaults, and then applies any
|
||||
/// rules in the given extra ini content to the plugin.
|
||||
/// </summary>
|
||||
/// <param name="extraTextIni">Ini content that remains after parsing an ini file. If null, only a rules reset is performed.</param>
|
||||
/// <returns>Any errors in parsing the <paramref name="extraTextIni"/> contents.</returns>
|
||||
private IEnumerable<string> ResetRules(INI extraTextIni)
|
||||
{
|
||||
if (extraTextIni != null)
|
||||
{
|
||||
// Strip "NewUnitsEnabled" from the Aftermath section.
|
||||
INISection amSection = extraTextIni.Sections["Aftermath"];
|
||||
if (amSection != null)
|
||||
{
|
||||
amSection.Keys.Remove("NewUnitsEnabled");
|
||||
}
|
||||
// Remove any sections known and handled / disallowed by the editor.
|
||||
extraTextIni.Sections.Remove("Digest");
|
||||
INITools.ClearDataFrom(extraTextIni, "Basic", (BasicSection)Map.BasicSection);
|
||||
INITools.ClearDataFrom(extraTextIni, "Map", Map.MapSection);
|
||||
extraTextIni.Sections.Remove("Steam");
|
||||
extraTextIni.Sections.Remove("TeamTypes");
|
||||
extraTextIni.Sections.Remove("Trigs");
|
||||
extraTextIni.Sections.Remove("MapPack");
|
||||
extraTextIni.Sections.Remove("Terrain");
|
||||
extraTextIni.Sections.Remove("OverlayPack");
|
||||
extraTextIni.Sections.Remove("Smudge");
|
||||
extraTextIni.Sections.Remove("Units");
|
||||
extraTextIni.Sections.Remove("Aircraft");
|
||||
extraTextIni.Sections.Remove("Ships");
|
||||
extraTextIni.Sections.Remove("Infantry");
|
||||
extraTextIni.Sections.Remove("Structures");
|
||||
extraTextIni.Sections.Remove("Base");
|
||||
extraTextIni.Sections.Remove("Waypoints");
|
||||
extraTextIni.Sections.Remove("CellTriggers");
|
||||
extraTextIni.Sections.Remove("Briefing");
|
||||
foreach (House house in Map.Houses)
|
||||
{
|
||||
INITools.ClearDataFrom(extraTextIni, house.Type.Name, house);
|
||||
}
|
||||
}
|
||||
if (this.rulesIni != null)
|
||||
{
|
||||
UpdateRules(rulesIni, this.Map);
|
||||
}
|
||||
if (this.aftermathRulesIni != null && Map.BasicSection.ExpansionEnabled)
|
||||
{
|
||||
UpdateRules(aftermathRulesIni, this.Map);
|
||||
}
|
||||
if (this.multiplayRulesIni != null && !this.Map.BasicSection.SoloMission)
|
||||
{
|
||||
UpdateRules(multiplayRulesIni, this.Map);
|
||||
}
|
||||
return extraTextIni == null ? null : UpdateRules(extraTextIni, this.Map);
|
||||
}
|
||||
|
||||
private INI rulesIni;
|
||||
private INI aftermathRulesIni;
|
||||
private INI multiplayRulesIni;
|
||||
|
||||
private readonly RaLandIniSection LandClear = new RaLandIniSection(90, 80, 60, 00, true);
|
||||
private readonly RaLandIniSection LandRough = new RaLandIniSection(80, 70, 40, 00, false);
|
||||
private readonly RaLandIniSection LandRoad = new RaLandIniSection(100, 100, 100, 00, true);
|
||||
private readonly RaLandIniSection LandWater = new RaLandIniSection(00, 00, 00, 100, false);
|
||||
private readonly RaLandIniSection LandRock = new RaLandIniSection(00, 00, 00, 00, false);
|
||||
private readonly RaLandIniSection LandBeach = new RaLandIniSection(80, 70, 40, 00, false);
|
||||
private readonly RaLandIniSection LandRiver = new RaLandIniSection(00, 00, 00, 00, false);
|
||||
|
||||
public static bool CheckForRAMap(INI contents)
|
||||
{
|
||||
return INITools.CheckForIniInfo(contents, "MapPack");
|
||||
@ -451,6 +490,8 @@ namespace MobiusEditor.RedAlert
|
||||
Map.BasicSection.Player = Map.HouseTypes.FirstOrDefault()?.Name;
|
||||
Map.BasicSection.Name = emptyMapName;
|
||||
UpdateBasePlayerHouse();
|
||||
// Initialises rules.
|
||||
ResetRules(null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -1221,7 +1262,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
if (!aftermathEnabled && aircraftType.IsExpansionUnit)
|
||||
{
|
||||
errors.Add(string.Format("Expansion unit '{0}' encountered, but expansion units are not enabled; enabling expansion units.", aircraftType.Name));
|
||||
errors.Add(string.Format("Expansion aircraft '{0}' encountered, but expansion units are not enabled; enabling expansion units.", aircraftType.Name));
|
||||
modified = true;
|
||||
Map.BasicSection.ExpansionEnabled = aftermathEnabled = true;
|
||||
}
|
||||
@ -1337,7 +1378,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
if (!aftermathEnabled && vesselType.IsExpansionUnit)
|
||||
{
|
||||
errors.Add(string.Format("Expansion unit '{0}' encountered, but expansion units are not enabled; enabling expansion units.", vesselType.Name));
|
||||
errors.Add(string.Format("Expansion ship '{0}' encountered, but expansion units are not enabled; enabling expansion units.", vesselType.Name));
|
||||
modified = true;
|
||||
Map.BasicSection.ExpansionEnabled = aftermathEnabled = true;
|
||||
}
|
||||
@ -1469,7 +1510,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
if (!aftermathEnabled && infantryType.IsExpansionUnit)
|
||||
{
|
||||
errors.Add(string.Format("Expansion infantry '{0}' encountered, but expansion units are not enabled; enabling expansion units.", infantryType.Name));
|
||||
errors.Add(string.Format("Expansion infantry unit '{0}' encountered, but expansion units are not enabled; enabling expansion units.", infantryType.Name));
|
||||
modified = true;
|
||||
Map.BasicSection.ExpansionEnabled = aftermathEnabled = true;
|
||||
}
|
||||
@ -2154,7 +2195,9 @@ namespace MobiusEditor.RedAlert
|
||||
// Won't trigger the notifications.
|
||||
Map.Triggers.Clear();
|
||||
Map.Triggers.AddRange(triggers);
|
||||
extraSections = ini.Sections;
|
||||
// init rules stuff
|
||||
errors.AddRange(this.ResetRules(ini));
|
||||
this.extraSections = ini.Sections;
|
||||
bool switchedToSolo = false;
|
||||
if (forceSoloMission && !basic.SoloMission)
|
||||
{
|
||||
@ -2183,22 +2226,43 @@ namespace MobiusEditor.RedAlert
|
||||
return errors;
|
||||
}
|
||||
|
||||
public void ReadRules(Byte[] rulesFile)
|
||||
public IEnumerable<string> ReadRules(Byte[] rulesFile)
|
||||
{
|
||||
this.rulesIni = ReadRulesFile(rulesFile);
|
||||
return UpdateRules(rulesIni, this.Map);
|
||||
}
|
||||
|
||||
public IEnumerable<string> ReadExpandRules(Byte[] rulesFile)
|
||||
{
|
||||
this.aftermathRulesIni = ReadRulesFile(rulesFile);
|
||||
if (this.Map.BasicSection.ExpansionEnabled)
|
||||
{
|
||||
return UpdateRules(aftermathRulesIni, this.Map);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<string> ReadMultiRules(Byte[] rulesFile)
|
||||
{
|
||||
this.multiplayRulesIni = ReadRulesFile(rulesFile);
|
||||
if (this.multiplayRulesIni != null && !this.Map.BasicSection.SoloMission)
|
||||
{
|
||||
return UpdateRules(multiplayRulesIni, this.Map);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private INI ReadRulesFile(Byte[] rulesFile)
|
||||
{
|
||||
if (rulesFile == null)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
// TODO read this. Might keep it as ini to just look up stuff from as fallback.
|
||||
}
|
||||
|
||||
public void PatchRules(Byte[] rulesUpdFile)
|
||||
{
|
||||
if (rulesUpdFile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// TODO read this. Might keep it as ini to just look up stuff from as fallback.
|
||||
Encoding encDOS = Encoding.GetEncoding(437);
|
||||
string iniText = encDOS.GetString(rulesFile);
|
||||
INI ini = new INI();
|
||||
ini.Parse(iniText);
|
||||
return ini;
|
||||
}
|
||||
|
||||
private Boolean FixCorruptTiles(Template template, byte iconValue, out byte newIconValue, out string type)
|
||||
@ -2295,31 +2359,91 @@ namespace MobiusEditor.RedAlert
|
||||
&& (templateType.IconMask == null || templateType.IconMask[newIconValue / templateType.IconWidth, newIconValue % templateType.IconWidth]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update rules according to the information in the given ini file.
|
||||
/// </summary>
|
||||
/// <param name="ini">ini file</param>
|
||||
/// <param name="map">Current map; used for ini parsing.</param>
|
||||
/// <returns>Any errors returned by the parsing process.</returns>
|
||||
private IEnumerable<string> UpdateRules(INI ini, Map map)
|
||||
{
|
||||
List<string> errors = new List<string>();
|
||||
errors.AddRange(UpdateGeneralRules(ini, map));
|
||||
errors.AddRange(UpdateBuildingRules(ini, map));
|
||||
if (ini == null)
|
||||
{
|
||||
errors.Add("Rules file is null!");
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.AddRange(UpdateLandTypeRules(ini, map));
|
||||
errors.AddRange(UpdateGeneralRules(ini, map));
|
||||
errors.AddRange(UpdateBuildingRules(ini, map));
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private IEnumerable<string> UpdateLandTypeRules(INI ini, Map map)
|
||||
{
|
||||
List<string> errors = new List<string>();
|
||||
this.ReadLandType(ini, map, "Clear", LandClear, errors);
|
||||
this.ReadLandType(ini, map, "Rough", LandRough, errors);
|
||||
this.ReadLandType(ini, map, "Road", LandRoad, errors);
|
||||
this.ReadLandType(ini, map, "Water", LandWater, errors);
|
||||
this.ReadLandType(ini, map, "Rock", LandRock, errors);
|
||||
this.ReadLandType(ini, map, "Beach", LandBeach, errors);
|
||||
this.ReadLandType(ini, map, "River", LandRiver, errors);
|
||||
return errors;
|
||||
}
|
||||
|
||||
private void ReadLandType(INI ini, Map map, string landType, RaLandIniSection landRules, List<string> errors)
|
||||
{
|
||||
if (ini == null || landRules == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
INISection landIni = ini[landType];
|
||||
if (landIni == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
List<(string, string)> parseErrors = INI.ParseSection(new MapContext(map, false), landIni, landRules, true);
|
||||
if (errors != null)
|
||||
{
|
||||
foreach ((string iniKey, string error) in parseErrors)
|
||||
{
|
||||
errors.Add("Custom rules error on [" + landType + "]: " + error.TrimEnd('.') + ". Value for \"" + iniKey + "\" is ignored.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (errors != null)
|
||||
{
|
||||
// Normally won't happen with the aforementioned system.
|
||||
errors.Add("Custom rules error on [" + landType + "]: " + e.Message.TrimEnd('.') + ". Rule updates for [" + landType + "] are ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IEnumerable<string> UpdateGeneralRules(INI ini, Map map)
|
||||
{
|
||||
List<string> errors = new List<string>();
|
||||
int? goldVal = GetIntRulesValue(ini, "General", "GoldValue", errors);
|
||||
int? goldVal = GetIntRulesValue(ini, "General", "GoldValue", false, errors);
|
||||
map.TiberiumOrGoldValue = goldVal ?? DefaultGoldValue;
|
||||
int? gemVal = GetIntRulesValue(ini, "General", "GemValue", errors);
|
||||
int? gemVal = GetIntRulesValue(ini, "General", "GemValue", false, errors);
|
||||
map.GemValue = gemVal ?? DefaultGemValue;
|
||||
int? radius = GetIntRulesValue(ini, "General", "DropZoneRadius", errors);
|
||||
int? radius = GetIntRulesValue(ini, "General", "DropZoneRadius", false, errors);
|
||||
map.DropZoneRadius = radius ?? DefaultDropZoneRadius;
|
||||
int? gapRadius = GetIntRulesValue(ini, "General", "GapRadius", errors);
|
||||
int? gapRadius = GetIntRulesValue(ini, "General", "GapRadius", false, errors);
|
||||
map.GapRadius = gapRadius ?? DefaultGapRadius;
|
||||
int? jamRadius = GetIntRulesValue(ini, "General", "RadarJamRadius", errors);
|
||||
int? jamRadius = GetIntRulesValue(ini, "General", "RadarJamRadius", false, errors);
|
||||
map.RadarJamRadius = jamRadius ?? DefaultJamRadius;
|
||||
return errors;
|
||||
}
|
||||
|
||||
private int? GetIntRulesValue(INI ini, string sec, string key, List<string> errors)
|
||||
private int? GetIntRulesValue(INI ini, string sec, string key, bool percentage, List<string> errors)
|
||||
{
|
||||
INISection section = ini.Sections[sec];
|
||||
if (section == null)
|
||||
@ -2327,15 +2451,22 @@ namespace MobiusEditor.RedAlert
|
||||
return null;
|
||||
}
|
||||
string valStr = section.TryGetValue(key);
|
||||
string valStrOrig = valStr;
|
||||
if (valStr != null)
|
||||
{
|
||||
valStr = valStr.Trim();
|
||||
if (percentage)
|
||||
{
|
||||
valStr = valStr.TrimEnd('%', ' ', '\t');
|
||||
}
|
||||
try
|
||||
{
|
||||
return Int32.Parse(valStr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
errors.Add(String.Format("Bad value for \"{0}\" rule in section [{1}]. Needs an integer number.", key, sec));
|
||||
errors.Add(String.Format("Bad value \"{0}\" for \"{1}\" rule in section [{2}]. Needs an integer number{3}.",
|
||||
valStrOrig, key, sec, percentage ? " percentage" : String.Empty));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -2456,7 +2587,7 @@ namespace MobiusEditor.RedAlert
|
||||
public bool Save(string path, FileType fileType, Bitmap customPreview, bool dontResavePreview)
|
||||
{
|
||||
string errors = Validate(false);
|
||||
if (errors != null)
|
||||
if (!String.IsNullOrWhiteSpace(errors))
|
||||
{
|
||||
MessageBox.Show(errors, "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
@ -3036,9 +3167,14 @@ namespace MobiusEditor.RedAlert
|
||||
|
||||
public string Validate(Boolean forWarnings)
|
||||
{
|
||||
StringBuilder sb;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (forWarnings)
|
||||
{
|
||||
// Check if map has name
|
||||
if (this.MapNameIsEmpty(this.Map.BasicSection.Name))
|
||||
{
|
||||
sb.AppendLine("Map name is empty. If you continue, the filename will be filled in as map name.");
|
||||
}
|
||||
// Check if the map or any of the scripting references ants, and if so, if their rules are filled in.
|
||||
UnitType[] antUs = { UnitTypes.Ant1, UnitTypes.Ant2, UnitTypes.Ant3 };
|
||||
BuildingType[] antBRaw = { BuildingTypes.Queen, BuildingTypes.Larva1, BuildingTypes.Larva2 };
|
||||
@ -3052,7 +3188,7 @@ namespace MobiusEditor.RedAlert
|
||||
// Nothing found.
|
||||
if (usedAntBldTypes.Count == 0 && usedAntUnitTypes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
return sb.ToString();
|
||||
}
|
||||
bool hasQueen = usedAntBldTypes.Any(bld => bld.ID == BuildingTypes.Queen.ID);
|
||||
List<String> types = new List<string>();
|
||||
@ -3068,7 +3204,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
if (types.Count == 0)
|
||||
{
|
||||
return null;
|
||||
return sb.ToString();
|
||||
}
|
||||
sb = new StringBuilder("The following ant units and structures were found on the map or in the scripting, but have no ini rules set to properly define their stats:");
|
||||
sb.Append("\n\n").Append(String.Join(", ", types.ToArray()));
|
||||
@ -3077,7 +3213,7 @@ namespace MobiusEditor.RedAlert
|
||||
sb.Append(" The definitions can be set in Settings → Map Settings → INI Rules & Tweaks.");
|
||||
return sb.ToString();
|
||||
}
|
||||
sb = new StringBuilder("Error(s) during map validation:");
|
||||
sb.AppendLine("Error(s) during map validation:");
|
||||
bool ok = true;
|
||||
int numAircraft = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft).Count();
|
||||
int numBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt).Count();
|
||||
@ -3829,109 +3965,38 @@ namespace MobiusEditor.RedAlert
|
||||
return flagColors;
|
||||
}
|
||||
|
||||
public bool IsVehiclePassable(LandType landType)
|
||||
|
||||
private RaLandIniSection GetLandInfo(LandType landType)
|
||||
{
|
||||
switch (landType)
|
||||
{
|
||||
case LandType.Clear:
|
||||
case LandType.Beach:
|
||||
case LandType.Road:
|
||||
case LandType.Rough:
|
||||
return true;
|
||||
case LandType.Rock:
|
||||
case LandType.River:
|
||||
case LandType.Water:
|
||||
return false;
|
||||
case LandType.Clear: return this.LandClear;
|
||||
case LandType.Beach: return this.LandBeach;
|
||||
case LandType.Road: return this.LandRoad;
|
||||
case LandType.Rough: return this.LandRough;
|
||||
case LandType.Rock: return this.LandRock;
|
||||
case LandType.River: return this.LandRiver;
|
||||
case LandType.Water: return this.LandWater;
|
||||
}
|
||||
// TODO make rules-obeying versions.
|
||||
switch (landType)
|
||||
{
|
||||
case LandType.Clear:
|
||||
break;
|
||||
case LandType.Beach:
|
||||
break;
|
||||
case LandType.Rock:
|
||||
break;
|
||||
case LandType.Road:
|
||||
break;
|
||||
case LandType.Water:
|
||||
break;
|
||||
case LandType.River:
|
||||
break;
|
||||
case LandType.Rough:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsBuildable(LandType landType)
|
||||
public bool IsLandUnitPassable(LandType landType)
|
||||
{
|
||||
switch (landType)
|
||||
{
|
||||
case LandType.Clear:
|
||||
case LandType.Road:
|
||||
return true;
|
||||
case LandType.Beach:
|
||||
case LandType.Rock:
|
||||
case LandType.Water:
|
||||
case LandType.River:
|
||||
case LandType.Rough:
|
||||
return false;
|
||||
}
|
||||
// TODO make rules-obeying versions.
|
||||
switch (landType)
|
||||
{
|
||||
case LandType.Clear:
|
||||
break;
|
||||
case LandType.Beach:
|
||||
break;
|
||||
case LandType.Rock:
|
||||
break;
|
||||
case LandType.Road:
|
||||
break;
|
||||
case LandType.Water:
|
||||
break;
|
||||
case LandType.River:
|
||||
break;
|
||||
case LandType.Rough:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
RaLandIniSection landInfo = GetLandInfo(landType);
|
||||
return landInfo != null && (landInfo.Foot > 0 || landInfo.Wheel > 0 || landInfo.Track > 0);
|
||||
}
|
||||
|
||||
public bool IsBoatPassable(LandType landType)
|
||||
{
|
||||
switch (landType)
|
||||
{
|
||||
case LandType.Water:
|
||||
return true;
|
||||
case LandType.Clear:
|
||||
case LandType.Beach:
|
||||
case LandType.Rock:
|
||||
case LandType.Road:
|
||||
case LandType.River:
|
||||
case LandType.Rough:
|
||||
return false;
|
||||
}
|
||||
// TODO make rules-obeying versions.
|
||||
switch (landType)
|
||||
{
|
||||
case LandType.Clear:
|
||||
break;
|
||||
case LandType.Beach:
|
||||
break;
|
||||
case LandType.Rock:
|
||||
break;
|
||||
case LandType.Road:
|
||||
break;
|
||||
case LandType.Water:
|
||||
break;
|
||||
case LandType.River:
|
||||
break;
|
||||
case LandType.Rough:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
RaLandIniSection landInfo = GetLandInfo(landType);
|
||||
return landInfo != null && landInfo.Float > 0;
|
||||
}
|
||||
|
||||
public bool IsBuildable(LandType landType)
|
||||
{
|
||||
RaLandIniSection landInfo = GetLandInfo(landType);
|
||||
return landInfo != null && landInfo.Buildable;
|
||||
}
|
||||
|
||||
private void BasicSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
|
@ -13,7 +13,9 @@
|
||||
// GNU General Public License along with permitted additional restrictions
|
||||
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
|
||||
using MobiusEditor.Model;
|
||||
using MobiusEditor.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -471,4 +473,36 @@ namespace MobiusEditor.RedAlert
|
||||
return Types;
|
||||
}
|
||||
}
|
||||
|
||||
public class RaLandIniSection
|
||||
{
|
||||
public RaLandIniSection(int footSpeed, int trackSpeed, int wheelSpeed, int floatSpeed, bool buildable)
|
||||
{
|
||||
this.Foot = footSpeed;
|
||||
this.Track = trackSpeed;
|
||||
this.Wheel = wheelSpeed;
|
||||
this.Float = floatSpeed;
|
||||
this.Buildable = buildable;
|
||||
}
|
||||
|
||||
[DefaultValue(0)]
|
||||
[TypeConverter(typeof(PercentageTypeConverter))]
|
||||
public int Foot { get; set; }
|
||||
|
||||
[DefaultValue(0)]
|
||||
[TypeConverter(typeof(PercentageTypeConverter))]
|
||||
public int Track { get; set; }
|
||||
|
||||
[DefaultValue(0)]
|
||||
[TypeConverter(typeof(PercentageTypeConverter))]
|
||||
public int Wheel { get; set; }
|
||||
|
||||
[DefaultValue(0)]
|
||||
[TypeConverter(typeof(PercentageTypeConverter))]
|
||||
public int Float { get; set; }
|
||||
|
||||
[TypeConverter(typeof(OneZeroBooleanTypeConverter))]
|
||||
[DefaultValue(0)]
|
||||
public bool Buildable { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2232,9 +2232,21 @@ namespace MobiusEditor.Render
|
||||
List<(int, int)> cellsVehImpassable = new List<(int, int)>();
|
||||
List<(int, int)> cellsUnbuildable = new List<(int, int)>();
|
||||
List<(int, int)> cellsBoatMovable = new List<(int, int)>();
|
||||
List<(int, int)> cellsRiver = new List<(int, int)>();
|
||||
// Possibly fetch the terrain type for clear terrain on this theater?
|
||||
TemplateType clear = plugin.Map.TemplateTypes.Where(t => (t.Flag & TemplateTypeFlag.Clear) == TemplateTypeFlag.Clear).FirstOrDefault();
|
||||
LandType clearLand = clear.LandTypes.Length > 0 ? clear.LandTypes[0] : LandType.Clear;
|
||||
// Caching this in advance for all types.
|
||||
LandType[] landTypes = (LandType[])Enum.GetValues(typeof(LandType));
|
||||
bool[][] passable = new bool[landTypes.Length][];
|
||||
for (int i = 0; i < landTypes.Length; i++)
|
||||
{
|
||||
LandType landType = landTypes[i];
|
||||
passable[i] = new bool[3];
|
||||
passable[i][0] = plugin.IsLandUnitPassable(landType); // isVehiclePassable
|
||||
passable[i][1] = plugin.IsBuildable(landType); // isBuildable
|
||||
passable[i][2] = plugin.IsBoatPassable(landType); // isBoatPassable
|
||||
}
|
||||
// The actual check.
|
||||
for (int y = visibleCells.Y; y < visibleCells.Bottom; ++y)
|
||||
{
|
||||
@ -2255,11 +2267,12 @@ namespace MobiusEditor.Render
|
||||
int icon = (template.Type.Flag & (TemplateTypeFlag.Clear | TemplateTypeFlag.RandomCell)) != TemplateTypeFlag.None ? 0 : template.Icon;
|
||||
land = icon < types.Length ? types[icon] : LandType.Clear;
|
||||
}
|
||||
// Exclude uninitialised terrain
|
||||
if (land != LandType.None)
|
||||
{
|
||||
bool isVehiclePassable = plugin.IsVehiclePassable(land);
|
||||
bool isBuildable = plugin.IsBuildable(land);
|
||||
bool isBoatPassable = plugin.IsBoatPassable(land);
|
||||
bool isVehiclePassable = passable[(int)land][0];
|
||||
bool isBuildable = passable[(int)land][1];
|
||||
bool isBoatPassable = passable[(int)land][2];
|
||||
if (isVehiclePassable)
|
||||
{
|
||||
if (!isBuildable)
|
||||
@ -2275,7 +2288,15 @@ namespace MobiusEditor.Render
|
||||
}
|
||||
else
|
||||
{
|
||||
cellsVehImpassable.Add((x, y));
|
||||
if (land == LandType.River || land == LandType.Water)
|
||||
{
|
||||
// Special case; impassable water.
|
||||
cellsRiver.Add((x, y));
|
||||
}
|
||||
else
|
||||
{
|
||||
cellsVehImpassable.Add((x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2288,6 +2309,7 @@ namespace MobiusEditor.Render
|
||||
bool disposeBmUnb = false;
|
||||
Bitmap bmWtr = null;
|
||||
bool disposeBmWtr = false;
|
||||
Bitmap bmRiv = null;
|
||||
int tileWidth = tileSize.Width;
|
||||
int tileHeight = tileSize.Height;
|
||||
float lineSize = tileWidth / 16.0f;
|
||||
@ -2317,10 +2339,28 @@ namespace MobiusEditor.Render
|
||||
disposeBmUnb = true;
|
||||
bmUnb = GenerateLinesBitmap(tileWidth, tileHeight, Color.FromArgb(255, 255, 85), lineSize, lineOffsetW, lineOffsetH, graphics);
|
||||
}
|
||||
if (bmWtr == null && cellsBoatMovable.Count > 0)
|
||||
if (bmWtr == null)
|
||||
{
|
||||
disposeBmWtr = true;
|
||||
bmWtr = GenerateLinesBitmap(tileSize.Width, tileSize.Height, Color.FromArgb(255, 255, 255), lineSize, lineOffsetW, lineOffsetH, graphics);
|
||||
if (cellsBoatMovable.Count > 0)
|
||||
{
|
||||
disposeBmWtr = true;
|
||||
bmWtr = GenerateLinesBitmap(tileSize.Width, tileSize.Height, Color.FromArgb(255, 255, 255), lineSize, lineOffsetW, lineOffsetH, graphics);
|
||||
}
|
||||
if (cellsRiver.Count > 0)
|
||||
{
|
||||
bmRiv = GenerateLinesBitmap(tileSize.Width, tileSize.Height, Color.FromArgb(0, 0, 255), lineSize, lineOffsetW, lineOffsetH, graphics);
|
||||
}
|
||||
}
|
||||
else if (cellsRiver.Count > 0)
|
||||
{
|
||||
bmRiv = new Bitmap(bmWtr);
|
||||
RegionData lines = ImageUtils.GetOutline(bmWtr.Size, bmWtr, 0.00f, 0x80, true);
|
||||
using (Graphics bgr = Graphics.FromImage(bmRiv))
|
||||
using (Region blueArea = new Region(lines))
|
||||
using (Brush blueBrush = new SolidBrush(Color.FromArgb(0, 0, 255)))
|
||||
{
|
||||
bgr.FillRegion(blueBrush, blueArea);
|
||||
}
|
||||
}
|
||||
// Finally, paint the actual cells.
|
||||
foreach ((int x, int y) in cellsVehImpassable)
|
||||
@ -2335,12 +2375,17 @@ namespace MobiusEditor.Render
|
||||
{
|
||||
graphics.DrawImage(bmWtr, new Rectangle(tileWidth * x, tileHeight * y, tileWidth, tileHeight), 0, 0, bmWtr.Width, bmWtr.Height, GraphicsUnit.Pixel, imageAttributes);
|
||||
}
|
||||
foreach ((int x, int y) in cellsRiver)
|
||||
{
|
||||
graphics.DrawImage(bmRiv, new Rectangle(tileWidth * x, tileHeight * y, tileWidth, tileHeight), 0, 0, bmRiv.Width, bmRiv.Height, GraphicsUnit.Pixel, imageAttributes);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (disposeBmImp && bmImp != null) try { bmImp.Dispose(); } catch { /* ignore */ }
|
||||
if (disposeBmUnb && bmUnb != null) try { bmUnb.Dispose(); } catch { /* ignore */ }
|
||||
if (disposeBmWtr && bmWtr != null) try { bmWtr.Dispose(); } catch { /* ignore */ }
|
||||
if (bmRiv != null) try { bmRiv.Dispose(); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,7 +264,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
INI ini = new INI();
|
||||
try
|
||||
{
|
||||
ini.Parse(value);
|
||||
ini.Parse(value ?? String.Empty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -2762,7 +2762,11 @@ namespace MobiusEditor.TiberianDawn
|
||||
{
|
||||
if (forWarnings)
|
||||
{
|
||||
// No warnings to check for TD/SS
|
||||
// Check if map has name
|
||||
if (this.MapNameIsEmpty(this.Map.BasicSection.Name))
|
||||
{
|
||||
return "Map name is empty. If you continue, the filename will be filled in as map name.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("Error(s) during map validation:");
|
||||
@ -3287,7 +3291,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
return flagColors;
|
||||
}
|
||||
|
||||
public virtual bool IsVehiclePassable(LandType landType)
|
||||
public virtual bool IsLandUnitPassable(LandType landType)
|
||||
{
|
||||
switch (landType)
|
||||
{
|
||||
@ -3304,6 +3308,11 @@ namespace MobiusEditor.TiberianDawn
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool IsBoatPassable(LandType landType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool IsBuildable(LandType landType)
|
||||
{
|
||||
switch (landType)
|
||||
@ -3321,11 +3330,6 @@ namespace MobiusEditor.TiberianDawn
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool IsBoatPassable(LandType landType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void BasicSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
|
@ -216,7 +216,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
public static readonly TemplateType Shore25 = new TemplateType(174, "sh25", 3, 2, new[] { TheaterTypes.Desert }, "III CII", null, ShoresWaterNorthDes);
|
||||
public static readonly TemplateType Shore26 = new TemplateType(175, "sh26", 3, 2, new[] { TheaterTypes.Desert }, "III XII", "111 011", ShoresWaterNorthDes);
|
||||
public static readonly TemplateType Shore27 = new TemplateType(176, "sh27", 4, 1, new[] { TheaterTypes.Desert }, "III III");
|
||||
public static readonly TemplateType Shore28 = new TemplateType(177, "sh28", 3, 1, new[] { TheaterTypes.Desert }, "III", (string)null, Point.Empty, ShoresWaterNorthDes);
|
||||
public static readonly TemplateType Shore28 = new TemplateType(177, "sh28", 3, 1, new[] { TheaterTypes.Desert }, "III", (string)null, Point.Empty, ShoresWaterNorthDes);
|
||||
public static readonly TemplateType Shore29 = new TemplateType(178, "sh29", 6, 2, new[] { TheaterTypes.Desert }, "IIIIII XCIIXX", "111111 011100");
|
||||
public static readonly TemplateType Shore30 = new TemplateType(179, "sh30", 2, 2, new[] { TheaterTypes.Desert }, "II IX", "11 10");
|
||||
public static readonly TemplateType Shore31 = new TemplateType(180, "sh31", 3, 3, new[] { TheaterTypes.Desert }, "III III III");
|
||||
|
@ -90,13 +90,13 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public T Get<T>(string key) where T : struct
|
||||
{
|
||||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
return (T)converter.ConvertFromString(this[key]);
|
||||
}
|
||||
|
||||
public void Set<T>(string key, T value) where T : struct
|
||||
{
|
||||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
this[key] = converter.ConvertToString(value);
|
||||
}
|
||||
|
||||
@ -147,6 +147,11 @@ namespace MobiusEditor.Utility
|
||||
return Keys.TryGetValue(key);
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
return Keys.Contains(key);
|
||||
}
|
||||
|
||||
public bool Empty => Keys.Count == 0;
|
||||
|
||||
public INISection(string name)
|
||||
@ -159,13 +164,13 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
string line = reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var m = INIHelpers.KeyValueRegex.Match(line);
|
||||
Match m = INIHelpers.KeyValueRegex.Match(line);
|
||||
if (m.Success)
|
||||
{
|
||||
Keys[m.Groups[1].Value] = m.Groups[2].Value.Trim();
|
||||
@ -175,7 +180,7 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public void Parse(string iniText)
|
||||
{
|
||||
using (var reader = new StringReader(iniText))
|
||||
using (StringReader reader = new StringReader(iniText))
|
||||
{
|
||||
Parse(reader);
|
||||
}
|
||||
@ -193,8 +198,8 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var lines = new List<string>(Keys.Count);
|
||||
foreach (var item in Keys)
|
||||
List<string> lines = new List<string>(Keys.Count);
|
||||
foreach ((string Key, string Value) item in Keys)
|
||||
{
|
||||
lines.Add(string.Format("{0}={1}", item.Key, item.Value));
|
||||
}
|
||||
@ -221,7 +226,7 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
if (!Sections.Contains(name))
|
||||
{
|
||||
var section = new INISection(name);
|
||||
INISection section = new INISection(name);
|
||||
Sections[name] = section;
|
||||
}
|
||||
return this[name];
|
||||
@ -249,7 +254,7 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public void AddRange(IEnumerable<INISection> sections)
|
||||
{
|
||||
foreach (var section in sections)
|
||||
foreach (INISection section in sections)
|
||||
{
|
||||
Add(section);
|
||||
}
|
||||
@ -271,7 +276,7 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var section = this[name];
|
||||
INISection section = this[name];
|
||||
Sections.Remove(name);
|
||||
return section;
|
||||
}
|
||||
@ -307,13 +312,13 @@ namespace MobiusEditor.Utility
|
||||
|
||||
while (true)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
string line = reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var m = INIHelpers.SectionRegex.Match(line);
|
||||
Match m = INIHelpers.SectionRegex.Match(line);
|
||||
if (m.Success)
|
||||
{
|
||||
currentSection = Sections.Add(m.Groups[1].Value);
|
||||
@ -333,7 +338,7 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public void Parse(string iniText)
|
||||
{
|
||||
using (var reader = new StringReader(iniText))
|
||||
using (StringReader reader = new StringReader(iniText))
|
||||
{
|
||||
Parse(reader);
|
||||
}
|
||||
@ -341,7 +346,7 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public IEnumerator<INISection> GetEnumerator()
|
||||
{
|
||||
foreach (var section in Sections)
|
||||
foreach (INISection section in Sections)
|
||||
{
|
||||
yield return section;
|
||||
}
|
||||
@ -364,10 +369,10 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public string ToString(string lineEnd)
|
||||
{
|
||||
var sections = new List<string>(Sections.Count);
|
||||
foreach (var item in Sections)
|
||||
List<string> sections = new List<string>(Sections.Count);
|
||||
foreach (INISection item in Sections)
|
||||
{
|
||||
var lines = new List<string>
|
||||
List<string> lines = new List<string>
|
||||
{
|
||||
string.Format("[{0}]", item.Name)
|
||||
};
|
||||
@ -419,7 +424,7 @@ namespace MobiusEditor.Utility
|
||||
internal INISectionDiff(INIDiffType type, INISection section)
|
||||
: this()
|
||||
{
|
||||
foreach (var keyValue in section.Keys)
|
||||
foreach ((string Key, string Value) keyValue in section.Keys)
|
||||
{
|
||||
keyDiff[keyValue.Key] = type;
|
||||
}
|
||||
@ -430,9 +435,9 @@ namespace MobiusEditor.Utility
|
||||
internal INISectionDiff(INISection leftSection, INISection rightSection)
|
||||
: this(INIDiffType.Removed, leftSection)
|
||||
{
|
||||
foreach (var keyValue in rightSection.Keys)
|
||||
foreach ((string Key, string Value) keyValue in rightSection.Keys)
|
||||
{
|
||||
var key = keyValue.Key;
|
||||
string key = keyValue.Key;
|
||||
if (keyDiff.ContainsKey(key))
|
||||
{
|
||||
if (leftSection[key] == rightSection[key])
|
||||
@ -467,8 +472,8 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var item in keyDiff)
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (KeyValuePair<string, INIDiffType> item in keyDiff)
|
||||
{
|
||||
sb.AppendLine(string.Format("{0} {1}", INIHelpers.DiffPrefix(item.Value), item.Key));
|
||||
}
|
||||
@ -500,14 +505,14 @@ namespace MobiusEditor.Utility
|
||||
public INIDiff(INI leftIni, INI rightIni)
|
||||
: this()
|
||||
{
|
||||
foreach (var leftSection in leftIni)
|
||||
foreach (INISection leftSection in leftIni)
|
||||
{
|
||||
sectionDiffs[leftSection.Name] = rightIni.Sections.Contains(leftSection.Name) ?
|
||||
new INISectionDiff(leftSection, rightIni[leftSection.Name]) :
|
||||
new INISectionDiff(INIDiffType.Removed, leftSection);
|
||||
}
|
||||
|
||||
foreach (var rightSection in rightIni)
|
||||
foreach (INISection rightSection in rightIni)
|
||||
{
|
||||
if (!leftIni.Sections.Contains(rightSection.Name))
|
||||
{
|
||||
@ -532,15 +537,15 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var item in sectionDiffs)
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (KeyValuePair<string, INISectionDiff> item in sectionDiffs)
|
||||
{
|
||||
sb.AppendLine(string.Format("{0} {1}", INIHelpers.DiffPrefix(item.Value.Type), item.Key));
|
||||
using (var reader = new StringReader(item.Value.ToString()))
|
||||
using (StringReader reader = new StringReader(item.Value.ToString()))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
string line = reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
@ -569,9 +574,9 @@ namespace MobiusEditor.Utility
|
||||
public static List<(string, string)> ParseSection<T>(ITypeDescriptorContext context, INISection section, T data, bool returnErrorsList)
|
||||
{
|
||||
List<(string, string)> errors = returnErrorsList ? new List<(string, string)>() : null;
|
||||
var propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetSetMethod() != null);
|
||||
foreach (var property in properties)
|
||||
PropertyDescriptorCollection propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
IEnumerable<PropertyInfo> properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetSetMethod() != null);
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
|
||||
{
|
||||
@ -582,7 +587,7 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
try
|
||||
{
|
||||
var converter = propertyDescriptors.Find(iniKey, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
|
||||
TypeConverter converter = propertyDescriptors.Find(iniKey, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
|
||||
if (converter.CanConvertFrom(context, typeof(string)))
|
||||
{
|
||||
property.SetValue(data, converter.ConvertFromString(context, section[iniKey]));
|
||||
@ -601,9 +606,9 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public static void RemoveHandledKeys<T>(INISection section, T data)
|
||||
{
|
||||
var propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetSetMethod() != null);
|
||||
foreach (var property in properties)
|
||||
PropertyDescriptorCollection propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
IEnumerable<PropertyInfo> properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetSetMethod() != null);
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
|
||||
{
|
||||
@ -615,19 +620,18 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public static void WriteSection<T>(ITypeDescriptorContext context, INISection section, T data)
|
||||
{
|
||||
var propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetGetMethod() != null);
|
||||
foreach (var property in properties)
|
||||
PropertyDescriptorCollection propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
IEnumerable<PropertyInfo> properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetGetMethod() != null);
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = property.GetValue(data);
|
||||
Object value = property.GetValue(data);
|
||||
if (property.PropertyType.IsValueType || (value != null))
|
||||
{
|
||||
var converter = propertyDescriptors.Find(property.Name, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
|
||||
TypeConverter converter = propertyDescriptors.Find(property.Name, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
|
||||
if (converter.CanConvertTo(context, typeof(string)))
|
||||
{
|
||||
section[property.Name] = converter.ConvertToString(context, value);
|
||||
|
@ -13,6 +13,7 @@
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
using MobiusEditor.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
@ -82,10 +83,26 @@ namespace MobiusEditor.Utility
|
||||
/// <returns>Null if the section was not found, otherwise the trimmed section.</returns>
|
||||
public static INISection ParseAndLeaveRemainder<T>(INI ini, string name, T data, MapContext context)
|
||||
{
|
||||
return ParseAndLeaveRemainder<T>(ini, name, data, context, false, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will find a section in the ini information, parse its data into the given data object, remove all
|
||||
/// keys managed by the data object from the ini section, and, if empty, remove the section from the ini.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data.</typeparam>
|
||||
/// <param name="ini">Ini object.</param>
|
||||
/// <param name="name">Name of the section.</param>
|
||||
/// <param name="data">Data object.</param>
|
||||
/// <param name="context">Map context to read data.</param>
|
||||
/// <returns>Null if the section was not found, otherwise the trimmed section.</returns>
|
||||
public static INISection ParseAndLeaveRemainder<T>(INI ini, string name, T data, MapContext context, bool returnErrors, out List<(string, string)> errors)
|
||||
{
|
||||
errors = null;
|
||||
var dataSection = ini.Sections[name];
|
||||
if (dataSection == null)
|
||||
return null;
|
||||
INI.ParseSection(context, dataSection, data);
|
||||
errors = INI.ParseSection(context, dataSection, data, returnErrors);
|
||||
INI.RemoveHandledKeys(dataSection, data);
|
||||
if (dataSection.Keys.Count() == 0)
|
||||
ini.Sections.Remove(name);
|
||||
|
@ -26,6 +26,8 @@ namespace MobiusEditor.Utility
|
||||
private Dictionary<GameType, string[]> modPathsPerGame;
|
||||
private readonly string looseFilePath;
|
||||
private MixfileManager mixFm;
|
||||
public MixfileManager ClassicFileManager { get { return mixFm; } }
|
||||
|
||||
private GameType currentGameType;
|
||||
|
||||
public String LoadRoot { get; private set; }
|
||||
@ -62,15 +64,6 @@ namespace MobiusEditor.Utility
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool LoadArchiveClassic(GameType gameType, String archivePath, bool isTheater, bool isContainer, bool canBeEmbedded, bool canUseNewFormat)
|
||||
{
|
||||
if (disposedValue)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
return mixFm.LoadArchive(gameType, archivePath, isTheater, isContainer, canBeEmbedded, canUseNewFormat);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
if (disposedValue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user