custom ini content support, encoding stuff and celltriggers preview.
This commit is contained in:
parent
ad7f86843d
commit
9fb1d69ac0
@ -365,14 +365,17 @@ Released on 14 Nov 2022 at 22:25 GMT
|
||||
--Unreleased--
|
||||
|
||||
* Added igloos (haystacks) to the Overlay in Sole Survivor's Winter theater.
|
||||
* Fixed refresh bug where a ghost image of the label indicating a heavy operation remained while repainting the map. The label is now only removed after the repaint.
|
||||
* Fixed issues with the editor not loading old missions with DOS special characters in them. Specific ini sections are now loaded and saved in specific text encodings; the maps are normally DOS-437, but its remaster-specific content supports UTF-8.
|
||||
* Fixed refresh bug where a ghost image of the label indicating a heavy operation remained while repainting the map. The label is now only removed after the following map repaint.
|
||||
* Fixed issues with the editor not loading old missions with DOS special characters in them. Specific ini sections are now loaded and saved in specific text encodings; the maps are normally DOS-437, but their remaster-specific contents support UTF-8.
|
||||
* Fixed an issue with TD triggers not linking to teamtypes if the teamtype reference has case differences compared to the actual Teamtype name.
|
||||
* Added turrets to RA ships, and rotors to helicopters.
|
||||
* When air units are enabled to be placed down, this no longer excludes winged airplanes.
|
||||
* When air units are enabled to be placed down, this no longer excludes winged airplanes. They'll just fly off the map, but that could be used for its cinematic effect in missions.
|
||||
* Added the ability to open .pgm archives, to easily check the contents of packed workshop maps. Export to this format is not supported, though.
|
||||
* Restored some of the "useless" orders possible to set on preplaced units; it turns out "Unload" on air units is the only order that makes them land on the intended spot.
|
||||
* The drawing order of overlapping objects is now based on the center of the graphics, rather than the bottom, which means buildings or trees will no longer overlap objects placed on their lower but unoccupied cells.
|
||||
* Repair Facilities are now treated as flat buildings, meaning they won't overlap things placed on their top cells.
|
||||
* Increased the size of waypoint labels.
|
||||
* When holding the mouse over a bib, the status bar will now also show if it is attached to a building.
|
||||
* The "Rules" section in the map settings has been renamed to "INI Rules & Tweaks", and is now also available for TD.
|
||||
* Custom ini keys that are added to the [Basic] and [Map] sections, and to any of the House sections, are now preserved, and editable in the "INI Rules & Tweaks" section. This will allow passive support for modded features.
|
||||
* Added a preview mode to Celltriggers.
|
||||
|
@ -581,6 +581,7 @@
|
||||
<Compile Include="Utility\GeneralUtils.cs" />
|
||||
<Compile Include="Utility\GenericBooleanTypeConverter.cs" />
|
||||
<Compile Include="Utility\INI.cs" />
|
||||
<Compile Include="Utility\INITools.cs" />
|
||||
<Compile Include="Utility\Keyboard.cs" />
|
||||
<Compile Include="Utility\ListItem.cs" />
|
||||
<Compile Include="Utility\Megafile.cs" />
|
||||
|
@ -30,15 +30,17 @@ namespace MobiusEditor.Controls
|
||||
playerComboBox.DataSource = plugin.Map.Houses.Select(h => h.Type.Name).ToArray();
|
||||
baseComboBox.DataSource = plugin.Map.Houses.Select(h => h.Type.Name).ToArray();
|
||||
var themeData = plugin.Map.ThemeTypes.ToList();
|
||||
string noTheme = plugin.Map.ThemeEmpty;
|
||||
themeData.Sort(new ExplorerComparer());
|
||||
themeData.RemoveAll(v => "No Theme".Equals(v, StringComparison.InvariantCultureIgnoreCase));
|
||||
themeData.Insert(0, "No Theme");
|
||||
themeData.RemoveAll(v => noTheme.Equals(v, StringComparison.OrdinalIgnoreCase));
|
||||
themeData.Insert(0, noTheme);
|
||||
themeComboBox.DataSource = themeData;
|
||||
// No need for matching to index here; [Basic] saves it by name, not index.
|
||||
var movData = plugin.Map.MovieTypes.ToList();
|
||||
string noMovie = plugin.Map.MovieEmpty;
|
||||
movData.Sort(new ExplorerComparer());
|
||||
movData.RemoveAll(v => "x".Equals(v, StringComparison.InvariantCultureIgnoreCase));
|
||||
movData.Insert(0, "x");
|
||||
movData.RemoveAll(v => noMovie.Equals(v, StringComparison.OrdinalIgnoreCase));
|
||||
movData.Insert(0, noMovie);
|
||||
introComboBox.DataSource = movData.ToArray();
|
||||
briefComboBox.DataSource = movData.ToArray();
|
||||
actionComboBox.DataSource = movData.ToArray();
|
||||
|
@ -132,7 +132,7 @@ namespace MobiusEditor.Controls
|
||||
}
|
||||
HashSet<string> allowedTriggers = new HashSet<string>(items);
|
||||
items = Trigger.None.Yield().Concat(Plugin.Map.Triggers.Select(t => t.Name).Where(t => allowedTriggers.Contains(t)).Distinct()).ToArray();
|
||||
int selectIndex = selected == null ? 0 : Enumerable.Range(0, items.Length).FirstOrDefault(x => String.Equals(items[x], selected, StringComparison.InvariantCultureIgnoreCase));
|
||||
int selectIndex = selected == null ? 0 : Enumerable.Range(0, items.Length).FirstOrDefault(x => String.Equals(items[x], selected, StringComparison.OrdinalIgnoreCase));
|
||||
triggerComboBox.DataSource = items;
|
||||
triggerComboBox.Enabled = !isAircraft && isOnMap;
|
||||
triggerToolTip = Map.MakeAllowedTriggersToolTip(filteredEvents, filteredActions);
|
||||
|
82
CnCTDRAMapEditor/Controls/RulesSettings.Designer.cs
generated
82
CnCTDRAMapEditor/Controls/RulesSettings.Designer.cs
generated
@ -43,43 +43,20 @@ namespace MobiusEditor.Controls
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(RulesSettings));
|
||||
this.txtRules = new System.Windows.Forms.TextBox();
|
||||
this.lblRules = new System.Windows.Forms.Label();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.lblTextEncoding = new System.Windows.Forms.Label();
|
||||
this.lblRaRules = new System.Windows.Forms.Label();
|
||||
this.txtRules = new System.Windows.Forms.TextBox();
|
||||
this.lblDosContent = new System.Windows.Forms.Label();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// txtRules
|
||||
//
|
||||
this.txtRules.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.txtRules.Location = new System.Drawing.Point(2, 97);
|
||||
this.txtRules.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtRules.Multiline = true;
|
||||
this.txtRules.Name = "txtRules";
|
||||
this.txtRules.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
|
||||
this.txtRules.Size = new System.Drawing.Size(396, 325);
|
||||
this.txtRules.TabIndex = 1;
|
||||
this.txtRules.Leave += new System.EventHandler(this.txtRules_Leave);
|
||||
//
|
||||
// lblRules
|
||||
//
|
||||
this.lblRules.AutoSize = true;
|
||||
this.lblRules.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblRules.Location = new System.Drawing.Point(3, 0);
|
||||
this.lblRules.Name = "lblRules";
|
||||
this.lblRules.Padding = new System.Windows.Forms.Padding(0, 0, 0, 2);
|
||||
this.lblRules.Size = new System.Drawing.Size(394, 67);
|
||||
this.lblRules.TabIndex = 0;
|
||||
this.lblRules.Text = resources.GetString("lblRules.Text");
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
this.tableLayoutPanel1.ColumnCount = 1;
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.lblRules, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.lblRaRules, 0, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.txtRules, 0, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.lblTextEncoding, 0, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.lblDosContent, 0, 0);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
@ -87,19 +64,44 @@ namespace MobiusEditor.Controls
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(400, 424);
|
||||
this.tableLayoutPanel1.TabIndex = 2;
|
||||
//
|
||||
// lblTextEncoding
|
||||
// lblRaRules
|
||||
//
|
||||
this.lblTextEncoding.AutoSize = true;
|
||||
this.lblTextEncoding.Location = new System.Drawing.Point(3, 67);
|
||||
this.lblTextEncoding.Name = "lblTextEncoding";
|
||||
this.lblTextEncoding.Padding = new System.Windows.Forms.Padding(0, 0, 0, 2);
|
||||
this.lblTextEncoding.Size = new System.Drawing.Size(378, 28);
|
||||
this.lblTextEncoding.TabIndex = 2;
|
||||
this.lblTextEncoding.Text = "Note that all text here is treated as DOS content, meaning it will be loaded and " +
|
||||
"saved using DOS-437 text encoding.";
|
||||
this.lblRaRules.AutoSize = true;
|
||||
this.lblRaRules.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblRaRules.Location = new System.Drawing.Point(3, 67);
|
||||
this.lblRaRules.Name = "lblRaRules";
|
||||
this.lblRaRules.Padding = new System.Windows.Forms.Padding(0, 0, 0, 2);
|
||||
this.lblRaRules.Size = new System.Drawing.Size(394, 15);
|
||||
this.lblRaRules.TabIndex = 0;
|
||||
this.lblRaRules.Text = "Rule changes concerning bibs, power and silo storage will be applied in the edito" +
|
||||
"r.";
|
||||
//
|
||||
// txtRules
|
||||
//
|
||||
this.txtRules.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.txtRules.Location = new System.Drawing.Point(2, 84);
|
||||
this.txtRules.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtRules.Multiline = true;
|
||||
this.txtRules.Name = "txtRules";
|
||||
this.txtRules.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
|
||||
this.txtRules.Size = new System.Drawing.Size(396, 338);
|
||||
this.txtRules.TabIndex = 1;
|
||||
this.txtRules.Leave += new System.EventHandler(this.txtRules_Leave);
|
||||
//
|
||||
// lblDosContent
|
||||
//
|
||||
this.lblDosContent.AutoSize = true;
|
||||
this.lblDosContent.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblDosContent.Location = new System.Drawing.Point(3, 0);
|
||||
this.lblDosContent.Name = "lblDosContent";
|
||||
this.lblDosContent.Padding = new System.Windows.Forms.Padding(0, 0, 0, 2);
|
||||
this.lblDosContent.Size = new System.Drawing.Size(394, 67);
|
||||
this.lblDosContent.TabIndex = 2;
|
||||
this.lblDosContent.Text = resources.GetString("lblDosContent.Text");
|
||||
//
|
||||
// RulesSettings
|
||||
//
|
||||
@ -116,9 +118,9 @@ namespace MobiusEditor.Controls
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.TextBox txtRules;
|
||||
private System.Windows.Forms.Label lblRules;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label lblTextEncoding;
|
||||
private System.Windows.Forms.Label lblDosContent;
|
||||
private System.Windows.Forms.Label lblRaRules;
|
||||
private System.Windows.Forms.TextBox txtRules;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace MobiusEditor.Controls
|
||||
txtRules.Text = iniText;
|
||||
if (!showRulesWarning)
|
||||
{
|
||||
lblRules.Visible = false;
|
||||
lblRaRules.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="lblRules.Text" xml:space="preserve">
|
||||
<value>The text below shows all INI sections not managed by the editor. Add any rule changes you need. Sections managed by the editor ([Basic], [Units], [Trigs] etc.) will be ignored. Rule changes will be applied in the editor. [Aftermath] can be added, but its "NewUnitsEnabled" option is managed by the editor (in "Basic") and will be ignored here.</value>
|
||||
<data name="lblDosContent.Text" xml:space="preserve">
|
||||
<value>The text below shows all INI content not managed by the editor. Sections that contain lists of data, like units, triggers, waypoints etc will be ignored when added here, as will any specific ini keys managed by the editor. Note that this text is treated as DOS content; special characters will be loaded and saved using DOS-437 text encoding.</value>
|
||||
</data>
|
||||
</root>
|
@ -93,7 +93,7 @@ namespace MobiusEditor.Controls
|
||||
string[] filteredActions = Plugin.Map.ActionTypes.Where(ev => Plugin.Map.TerrainActionTypes.Contains(ev)).Distinct().ToArray();
|
||||
HashSet<string> allowedTriggers = new HashSet<string>(items);
|
||||
items = Trigger.None.Yield().Concat(Plugin.Map.Triggers.Select(t => t.Name).Where(t => allowedTriggers.Contains(t)).Distinct()).ToArray();
|
||||
int selectIndex = selected == null ? 0 : Enumerable.Range(0, items.Length).FirstOrDefault(x => String.Equals(items[x], selected, StringComparison.InvariantCultureIgnoreCase));
|
||||
int selectIndex = selected == null ? 0 : Enumerable.Range(0, items.Length).FirstOrDefault(x => String.Equals(items[x], selected, StringComparison.OrdinalIgnoreCase));
|
||||
triggerComboBox.DataSource = items;
|
||||
triggerComboBox.SelectedIndex = selectIndex;
|
||||
triggerToolTip = Map.MakeAllowedTriggersToolTip(filteredEvents, filteredActions);
|
||||
|
@ -24,6 +24,7 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace MobiusEditor.Dialogs
|
||||
@ -108,7 +109,7 @@ namespace MobiusEditor.Dialogs
|
||||
{
|
||||
settingsTreeView.Nodes.Add("CRATES", "Crates");
|
||||
}
|
||||
settingsTreeView.Nodes.Add("RULES", this.plugin.GameType == GameType.RedAlert ? "Rules" : "Unmanaged INI");
|
||||
settingsTreeView.Nodes.Add("RULES", "INI Rules && Tweaks");
|
||||
if (this.plugin.GameType != GameType.SoleSurvivor)
|
||||
{
|
||||
settingsTreeView.Nodes.Add("BRIEFING", "Briefing");
|
||||
@ -317,8 +318,16 @@ namespace MobiusEditor.Dialogs
|
||||
|
||||
}
|
||||
}
|
||||
// Check if RA rules were changed.
|
||||
bool rulesChanged = plugin.GameType == GameType.RedAlert && !(this.ExtraIniText ?? String.Empty).Equals(originalExtraIniText);
|
||||
// Combine diacritics into their characters, and remove characters not included in DOS-437.
|
||||
string normalised = (this.ExtraIniText ?? String.Empty).Normalize(NormalizationForm.FormC);
|
||||
Encoding dos437 = Encoding.GetEncoding(437);
|
||||
// DOS chars excluding specials at the start and end. Explicitly add tab, then the normal range from 32 to 254.
|
||||
HashSet<Char> dos437chars = String.Concat("\t".Yield().Concat(Enumerable.Range(32, 256 - 32 - 1).Select(i => dos437.GetString(new Byte[] { (byte)i })))).ToHashSet();
|
||||
normalised = new String(normalised.Where(ch => dos437chars.Contains(ch)).ToArray());
|
||||
// Check if RA rules were changed. 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(originalExtraIniText ?? String.Empty, "[\\r\\n]+", "\n").Trim('\n');
|
||||
bool rulesChanged = plugin.GameType == GameType.RedAlert && !checkTextOrig.Equals(checkTextNew, StringComparison.OrdinalIgnoreCase);
|
||||
// Check if RA expansion units were disabled.
|
||||
bool expansionWarn = plugin.GameType == GameType.RedAlert && expansionWasEnabled
|
||||
&& basicSettingsTracker.TryGetMember("ExpansionEnabled", out object res) && (res is bool expOn) && !expOn;
|
||||
|
@ -317,7 +317,7 @@ namespace MobiusEditor.Dialogs
|
||||
}
|
||||
if (hasChanges)
|
||||
{
|
||||
DialogResult dr = MessageBox.Show("Teams have been changed! Are you sure you want to cancel?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
DialogResult dr = MessageBox.Show(this, "Teams have been changed! Are you sure you want to cancel?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
if (dr == DialogResult.Yes)
|
||||
return;
|
||||
this.DialogResult = DialogResult.None;
|
||||
@ -499,22 +499,22 @@ namespace MobiusEditor.Dialogs
|
||||
else if (curName.Length > maxLength)
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Team name is longer than {0} characters.", maxLength), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Team name is longer than {0} characters.", maxLength), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else if (TeamType.None.Equals(curName, StringComparison.InvariantCultureIgnoreCase))
|
||||
else if (TeamType.IsEmpty(curName))
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Team name 'None' is reserved and cannot be used."), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Team name '{0}' is reserved and cannot be used.", TeamType.None), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else if (!INIHelpers.IsValidKey(curName))
|
||||
else if (!INITools.IsValidKey(curName))
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Team name '{0}' contains illegal characters. This format only supports simple ASCII, and cannot contain '=', '[' or ']'.", curName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Team name '{0}' contains illegal characters. This format only supports simple ASCII, and cannot contain '=', '[' or ']'.", curName.ToUpperInvariant()), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else if (teamTypes.Where(t => (t != SelectedTeamType) && t.Name.Equals(curName, StringComparison.InvariantCultureIgnoreCase)).Any())
|
||||
else if (teamTypes.Where(t => (t != SelectedTeamType) && t.Name.Equals(curName, StringComparison.OrdinalIgnoreCase)).Any())
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Team with name '{0}' already exists.", curName.ToUpperInvariant()), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Team with name '{0}' already exists.", curName.ToUpperInvariant()), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -274,7 +274,7 @@ namespace MobiusEditor.Dialogs
|
||||
}
|
||||
if (hasChanges)
|
||||
{
|
||||
DialogResult dr = MessageBox.Show("Triggers have been changed! Are you sure you want to cancel?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
DialogResult dr = MessageBox.Show(this, "Triggers have been changed! Are you sure you want to cancel?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
if (dr == DialogResult.Yes)
|
||||
return;
|
||||
this.DialogResult = DialogResult.None;
|
||||
@ -443,22 +443,22 @@ namespace MobiusEditor.Dialogs
|
||||
else if (curName.Length > maxLength)
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Trigger name is longer than {0} characters.", maxLength), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Trigger name is longer than {0} characters.", maxLength), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else if (Trigger.IsEmpty(curName))
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Trigger name 'None' is reserved and cannot be used."), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Trigger name '{0}' is reserved and cannot be used.", Trigger.None), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else if (!INIHelpers.IsValidKey(curName))
|
||||
else if (!INITools.IsValidKey(curName))
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Trigger name '{0}' contains illegal characters. This format only supports simple ASCII, and cannot contain '=', '[' or ']'.", curName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Trigger name '{0}' contains illegal characters. This format only supports simple ASCII, and cannot contain '=', '[' or ']'.", curName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else if (triggers.Where(t => (t != SelectedTrigger) && t.Name.Equals(curName, StringComparison.InvariantCultureIgnoreCase)).Any())
|
||||
else if (triggers.Where(t => (t != SelectedTrigger) && t.Name.Equals(curName, StringComparison.OrdinalIgnoreCase)).Any())
|
||||
{
|
||||
e.CancelEdit = true;
|
||||
MessageBox.Show(string.Format("Trigger with name '{0}' already exists.", curName.ToUpperInvariant()), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(this, string.Format("Trigger with name '{0}' already exists.", curName.ToUpperInvariant()), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -830,8 +830,9 @@ namespace MobiusEditor.Dialogs
|
||||
actionValueComboBox.Visible = true;
|
||||
actionValueComboBox.DisplayMember = "Label";
|
||||
actionValueComboBox.ValueMember = "Value";
|
||||
ExplorerComparer sorter = new ExplorerComparer();
|
||||
// First video is the "None" entry; expose as -1.
|
||||
var movData = plugin.Map.MovieTypes.Select((t, i) => new ListItem<long>(i - 1, t)).OrderBy(t => t.Label, new ExplorerComparer()).ToList();
|
||||
var movData = plugin.Map.MovieTypes.Select((t, i) => new ListItem<long>(i - 1, t)).OrderBy(t => t.Label, sorter).ToList();
|
||||
var movDefItem = movData.Where(t => t.Value == -1).FirstOrDefault();
|
||||
if (movDefItem != null)
|
||||
{
|
||||
@ -848,7 +849,7 @@ namespace MobiusEditor.Dialogs
|
||||
actionValueComboBox.ValueMember = "Value";
|
||||
var vocData = new ListItem<long>(-1, "None").Yield().Concat(
|
||||
RedAlert.ActionDataTypes.VocDesc.Select((t, i) => new ListItem<long>(i, t + " (" + RedAlert.ActionDataTypes.VocNames[i] + ")"))
|
||||
.Where(t => !String.Equals(RedAlert.ActionDataTypes.VocNames[t.Value], "x", StringComparison.InvariantCultureIgnoreCase))).ToArray();
|
||||
.Where(t => !String.Equals(RedAlert.ActionDataTypes.VocNames[t.Value], "x", StringComparison.OrdinalIgnoreCase))).ToArray();
|
||||
actionValueComboBox.DataSource = vocData;
|
||||
actionValueComboBox.DataBindings.Add("SelectedValue", triggerAction, "Data");
|
||||
actionValueComboBox.SelectedValue = ListItem.CheckInList(data, vocData);
|
||||
@ -859,7 +860,7 @@ namespace MobiusEditor.Dialogs
|
||||
actionValueComboBox.ValueMember = "Value";
|
||||
var voxData = new ListItem<long>(-1, "None").Yield().Concat(
|
||||
RedAlert.ActionDataTypes.VoxDesc.Select((t, i) => new ListItem<long>(i, t + " (" + RedAlert.ActionDataTypes.VoxNames[i] + ")"))
|
||||
.Where(t => !String.Equals(RedAlert.ActionDataTypes.VoxNames[t.Value], "none", StringComparison.InvariantCultureIgnoreCase))).ToArray();
|
||||
.Where(t => !String.Equals(RedAlert.ActionDataTypes.VoxNames[t.Value], "none", StringComparison.OrdinalIgnoreCase))).ToArray();
|
||||
actionValueComboBox.DataSource = voxData;
|
||||
actionValueComboBox.DataBindings.Add("SelectedValue", triggerAction, "Data");
|
||||
actionValueComboBox.SelectedValue = ListItem.CheckInList(data, voxData);
|
||||
@ -918,7 +919,7 @@ namespace MobiusEditor.Dialogs
|
||||
{
|
||||
if (Trigger.CheckForChanges(triggers, backupTriggers))
|
||||
{
|
||||
DialogResult dr = MessageBox.Show("Warning! There are changes in the triggers. This function works best if the triggers match the state of the currently edited map. Are you sure you want to continue?", "Triggers check", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
DialogResult dr = MessageBox.Show(this, "Warning! There are changes in the triggers. This function works best if the triggers match the state of the currently edited map. Are you sure you want to continue?", "Triggers check", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
if (dr == DialogResult.No)
|
||||
{
|
||||
return;
|
||||
@ -927,7 +928,7 @@ namespace MobiusEditor.Dialogs
|
||||
string[] errors = plugin.CheckTriggers(this.triggers, true, false, false, out _, false, out _)?.ToArray();
|
||||
if (errors == null || errors.Length == 0)
|
||||
{
|
||||
MessageBox.Show("No issues were encountered.", "Triggers check", MessageBoxButtons.OK);
|
||||
MessageBox.Show(this, "No issues were encountered.", "Triggers check", MessageBoxButtons.OK);
|
||||
return;
|
||||
}
|
||||
using (ErrorMessageBox emb = new ErrorMessageBox())
|
||||
|
@ -30,6 +30,7 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using static MobiusEditor.Utility.SimpleMultiThreading;
|
||||
|
||||
@ -526,11 +527,13 @@ namespace MobiusEditor
|
||||
PropertyTracker<BasicSection> basicSettings = new PropertyTracker<BasicSection>(plugin.Map.BasicSection);
|
||||
PropertyTracker<BriefingSection> briefingSettings = new PropertyTracker<BriefingSection>(plugin.Map.BriefingSection);
|
||||
PropertyTracker<SoleSurvivor.CratesSection> cratesSettings = null;
|
||||
if (plugin is SoleSurvivor.GamePlugin ssPlugin)
|
||||
if (plugin.GameType == GameType.SoleSurvivor && plugin is SoleSurvivor.GamePlugin ssPlugin)
|
||||
{
|
||||
cratesSettings = new PropertyTracker<SoleSurvivor.CratesSection>(ssPlugin.CratesSection);
|
||||
}
|
||||
string extraIniText = plugin.ExtraIniText;
|
||||
if (extraIniText.Trim('\r', '\n').Length == 0)
|
||||
extraIniText = String.Empty;
|
||||
Dictionary<House, PropertyTracker<House>> houseSettingsTrackers = plugin.Map.Houses.ToDictionary(h => h, h => new PropertyTracker<House>(h));
|
||||
using (MapSettingsDialog msd = new MapSettingsDialog(plugin, basicSettings, briefingSettings, cratesSettings, houseSettingsTrackers, extraIniText))
|
||||
{
|
||||
@ -550,17 +553,26 @@ namespace MobiusEditor
|
||||
hasChanges = true;
|
||||
houseSettingsTracker.Commit();
|
||||
}
|
||||
if (!extraIniText.Equals(msd.ExtraIniText, StringComparison.InvariantCultureIgnoreCase))
|
||||
// Combine diacritics into their characters, and remove characters not included in DOS-437.
|
||||
string normalised = (msd.ExtraIniText ?? String.Empty).Normalize(NormalizationForm.FormC);
|
||||
Encoding dos437 = Encoding.GetEncoding(437);
|
||||
// DOS chars excluding specials at the start and end. Explicitly add tab, then the normal range from 32 to 254.
|
||||
HashSet<Char> dos437chars = String.Concat("\t".Yield().Concat(Enumerable.Range(32, 256 - 32 - 1).Select(i => dos437.GetString(new Byte[] { (byte)i })))).ToHashSet();
|
||||
normalised = new String(normalised.Where(ch => dos437chars.Contains(ch)).ToArray());
|
||||
// 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))
|
||||
{
|
||||
try
|
||||
{
|
||||
plugin.ExtraIniText = msd.ExtraIniText;
|
||||
plugin.ExtraIniText = normalised;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Errors occurred when applying rule changes:\n\n" + ex.Message, GetProgramVersionTitle());
|
||||
}
|
||||
rulesChanged = true;
|
||||
rulesChanged = plugin.GameType == GameType.RedAlert;
|
||||
hasChanges = true;
|
||||
}
|
||||
plugin.Dirty = hasChanges;
|
||||
@ -1009,7 +1021,7 @@ namespace MobiusEditor
|
||||
{
|
||||
iniContents = GeneralUtils.GetIniContents(iniFile, fileType);
|
||||
}
|
||||
if (iniContents == null || !GeneralUtils.CheckForIniInfo(iniContents, "Map") || !GeneralUtils.CheckForIniInfo(iniContents, "Basic"))
|
||||
if (iniContents == null || !INITools.CheckForIniInfo(iniContents, "Map") || !INITools.CheckForIniInfo(iniContents, "Basic"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -12,12 +12,16 @@
|
||||
// distributed with this program. You should have received a copy of the
|
||||
// GNU General Public License along with permitted additional restrictions
|
||||
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
|
||||
using System.Drawing;
|
||||
|
||||
namespace MobiusEditor.Model
|
||||
{
|
||||
public class CellTrigger
|
||||
{
|
||||
public string Trigger { get; set; } = Model.Trigger.None;
|
||||
|
||||
public Color Tint { get; set; } = Color.White;
|
||||
|
||||
public CellTrigger(string trigger)
|
||||
{
|
||||
Trigger = trigger;
|
||||
|
@ -169,12 +169,15 @@ namespace MobiusEditor.Model
|
||||
|
||||
public readonly string[] MissionTypes;
|
||||
|
||||
private const string defaultMission = "Guard";
|
||||
private readonly string inputMissionArmed;
|
||||
private readonly string inputMissionUnarmed;
|
||||
private readonly string inputMissionAircraft;
|
||||
private readonly string inputMissionHarvest;
|
||||
|
||||
public readonly string DefaultMissionArmed;
|
||||
|
||||
public readonly string DefaultMissionUnarmed;
|
||||
|
||||
public readonly string DefaultMissionAircraft;
|
||||
|
||||
public readonly string DefaultMissionHarvest;
|
||||
|
||||
public readonly List<DirectionType> BuildingDirectionTypes;
|
||||
@ -279,8 +282,9 @@ namespace MobiusEditor.Model
|
||||
|
||||
public House[] HousesIncludingNone;
|
||||
|
||||
public string MovieEmpty;
|
||||
public readonly List<string> MovieTypes;
|
||||
|
||||
public readonly string ThemeEmpty;
|
||||
public readonly List<string> ThemeTypes;
|
||||
|
||||
public int TiberiumOrGoldValue { get; set; }
|
||||
@ -308,9 +312,10 @@ namespace MobiusEditor.Model
|
||||
IEnumerable<TerrainType> terrainTypes, IEnumerable<OverlayType> overlayTypes, IEnumerable<SmudgeType> smudgeTypes,
|
||||
IEnumerable<string> eventTypes, IEnumerable<string> cellEventTypes, IEnumerable<string> unitEventTypes, IEnumerable<string> structureEventTypes, IEnumerable<string> terrainEventTypes,
|
||||
IEnumerable<string> actionTypes, IEnumerable<string> cellActionTypes, IEnumerable<string> unitActionTypes, IEnumerable<string> structureActionTypes, IEnumerable<string> terrainActionTypes,
|
||||
IEnumerable<string> missionTypes, IEnumerable<DirectionType> unitDirectionTypes, IEnumerable<DirectionType> buildingDirectionTypes, IEnumerable<InfantryType> infantryTypes,
|
||||
IEnumerable<UnitType> unitTypes, IEnumerable<BuildingType> buildingTypes, IEnumerable<TeamMission> teamMissionTypes,
|
||||
IEnumerable<ITechnoType> teamTechnoTypes, IEnumerable<Waypoint> waypoints, IEnumerable<string> movieTypes, IEnumerable<string> themeTypes)
|
||||
IEnumerable<string> missionTypes, string armedMission, string unarmedMission, string harvestMission, string aircraftMission,
|
||||
IEnumerable<DirectionType> unitDirectionTypes, IEnumerable<DirectionType> buildingDirectionTypes, IEnumerable<InfantryType> infantryTypes,
|
||||
IEnumerable<UnitType> unitTypes, IEnumerable<BuildingType> buildingTypes, IEnumerable<TeamMission> teamMissionTypes,IEnumerable<ITechnoType> teamTechnoTypes,
|
||||
IEnumerable<Waypoint> waypoints, IEnumerable<string> movieTypes, string emptyMovie, IEnumerable<string> themeTypes, string emptyTheme)
|
||||
{
|
||||
MapSection = new MapSection(cellSize);
|
||||
BasicSection = basicSection;
|
||||
@ -324,23 +329,28 @@ namespace MobiusEditor.Model
|
||||
OverlayTypes = new List<OverlayType>(overlayTypes);
|
||||
SmudgeTypes = new List<SmudgeType>(smudgeTypes);
|
||||
EventTypes = eventTypes.ToArray();
|
||||
CellEventTypes = cellEventTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
UnitEventTypes = unitEventTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
StructureEventTypes = structureEventTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
TerrainEventTypes = terrainEventTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
CellActionTypes = cellActionTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
UnitActionTypes = unitActionTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
StructureActionTypes = structureActionTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
TerrainActionTypes = terrainActionTypes.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
CellEventTypes = cellEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
UnitEventTypes = unitEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
StructureEventTypes = structureEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
TerrainEventTypes = terrainEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
CellActionTypes = cellActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
UnitActionTypes = unitActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
StructureActionTypes = structureActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
TerrainActionTypes = terrainActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
ActionTypes = actionTypes.ToArray();
|
||||
MissionTypes = missionTypes.ToArray();
|
||||
DefaultMissionArmed = MissionTypes.Where(m => m.Equals("Guard")).FirstOrDefault() ?? MissionTypes.First();
|
||||
DefaultMissionUnarmed = MissionTypes.Where(m => m.Equals("Stop")).FirstOrDefault() ?? MissionTypes.First();
|
||||
// Reverts to "stop" if there are no resources (RA indoor)
|
||||
DefaultMissionHarvest = OverlayTypes.Any(ov => ov.IsResource) ? MissionTypes.Where(m => m.Equals("Harvest")).FirstOrDefault() ?? DefaultMissionUnarmed : DefaultMissionUnarmed;
|
||||
// In TD, at lease, only Unload will make them stay on the spot as expected.
|
||||
DefaultMissionAircraft = MissionTypes.Where(m => m.Equals("Unload")).FirstOrDefault() ?? MissionTypes.First();
|
||||
string defMission = MissionTypes.Where(m => m.Equals(defaultMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? MissionTypes.First();
|
||||
inputMissionArmed = armedMission;
|
||||
inputMissionUnarmed = unarmedMission;
|
||||
inputMissionAircraft = harvestMission;
|
||||
inputMissionHarvest = aircraftMission;
|
||||
DefaultMissionArmed = MissionTypes.Where(m => m.Equals(armedMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? defMission;
|
||||
DefaultMissionUnarmed = MissionTypes.Where(m => m.Equals(unarmedMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? defMission;
|
||||
// Reverts to "Stop" if there are no resources (RA indoor)
|
||||
DefaultMissionHarvest = OverlayTypes.Any(ov => ov.IsResource) ? MissionTypes.Where(m => m.Equals(harvestMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? DefaultMissionUnarmed : DefaultMissionUnarmed;
|
||||
// Only "Unload" will make them stay on the spot as expected.
|
||||
DefaultMissionAircraft = MissionTypes.Where(m => m.Equals(aircraftMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? defMission;
|
||||
UnitDirectionTypes = new List<DirectionType>(unitDirectionTypes);
|
||||
BuildingDirectionTypes = new List<DirectionType>(buildingDirectionTypes);
|
||||
AllInfantryTypes = new List<InfantryType>(infantryTypes);
|
||||
@ -348,7 +358,9 @@ namespace MobiusEditor.Model
|
||||
BuildingTypes = new List<BuildingType>(buildingTypes);
|
||||
TeamMissionTypes = teamMissionTypes.ToArray();
|
||||
AllTeamTechnoTypes = new List<ITechnoType>(teamTechnoTypes);
|
||||
MovieEmpty = emptyMovie;
|
||||
MovieTypes = new List<string>(movieTypes);
|
||||
ThemeEmpty = emptyTheme;
|
||||
ThemeTypes = new List<string>(themeTypes);
|
||||
|
||||
Metrics = new CellMetrics(cellSize);
|
||||
@ -803,14 +815,15 @@ namespace MobiusEditor.Model
|
||||
Waypoint[] wpPreview = new Waypoint[Waypoints.Length + 1];
|
||||
Array.Copy(Waypoints, wpPreview, Waypoints.Length);
|
||||
wpPreview[Waypoints.Length] = new Waypoint("", null);
|
||||
// This is a shallow clone; the map is new, but the placed content all still reference the original objects.
|
||||
// This is a shallow clone; the map is new, but the placed contents all still reference the original objects.
|
||||
// These shallow copies are used for map preview during editing, where dummy objects can be added without any issue.
|
||||
var map = new Map(BasicSection, Theater, Metrics.Size, HouseType, HouseTypesIncludingNone,
|
||||
FlagColors, TheaterTypes, TemplateTypes, TerrainTypes, OverlayTypes, SmudgeTypes,
|
||||
EventTypes, CellEventTypes, UnitEventTypes, StructureEventTypes, TerrainEventTypes,
|
||||
ActionTypes, CellActionTypes, UnitActionTypes, StructureActionTypes, TerrainActionTypes,
|
||||
MissionTypes, UnitDirectionTypes, BuildingDirectionTypes, AllInfantryTypes, AllUnitTypes, BuildingTypes, TeamMissionTypes,
|
||||
AllTeamTechnoTypes, wpPreview, MovieTypes, ThemeTypes)
|
||||
MissionTypes, inputMissionArmed, inputMissionUnarmed, inputMissionHarvest, inputMissionAircraft,
|
||||
UnitDirectionTypes, BuildingDirectionTypes, AllInfantryTypes, AllUnitTypes, BuildingTypes, TeamMissionTypes,
|
||||
AllTeamTechnoTypes, wpPreview, MovieTypes, MovieEmpty, ThemeTypes, ThemeEmpty)
|
||||
{
|
||||
TopLeft = TopLeft,
|
||||
Size = Size
|
||||
@ -1387,10 +1400,10 @@ namespace MobiusEditor.Model
|
||||
private void CleanUpTriggers(List<Trigger> triggers, Dictionary<object, string> undoList, Dictionary<object, string> redoList, Dictionary<CellTrigger, int> cellTriggerLocations)
|
||||
{
|
||||
// Clean techno types
|
||||
HashSet<string> availableTriggers = triggers.Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> availableUnitTriggers = FilterUnitTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> availableBuildingTriggers = FilterStructureTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> availableTerrainTriggers = FilterTerrainTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> availableTriggers = triggers.Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> availableUnitTriggers = FilterUnitTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> availableBuildingTriggers = FilterStructureTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> availableTerrainTriggers = FilterTerrainTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (ITechno techno in GetAllTechnos())
|
||||
{
|
||||
if (techno is Infantry infantry)
|
||||
@ -1465,7 +1478,7 @@ namespace MobiusEditor.Model
|
||||
|
||||
private void CleanUpCellTriggers(List<Trigger> triggers, Dictionary<object, string> undoList, Dictionary<object, string> redoList, Dictionary<CellTrigger, int> cellTriggerLocations)
|
||||
{
|
||||
HashSet<string> placeableTrigs = FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> placeableTrigs = FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
List<int> cellsToClear = new List<int>();
|
||||
foreach ((int cell, CellTrigger value) in CellTriggers)
|
||||
{
|
||||
|
@ -92,7 +92,7 @@ namespace MobiusEditor.Model
|
||||
ThumbnailHeight = IconHeight;
|
||||
Theaters = theaters;
|
||||
Flag = flag;
|
||||
MaskOverrides = new Dictionary<string, bool[,]>(StringComparer.InvariantCultureIgnoreCase);
|
||||
MaskOverrides = new Dictionary<string, bool[,]>(StringComparer.OrdinalIgnoreCase);
|
||||
GroupTiles = new string[0];
|
||||
}
|
||||
|
||||
|
@ -79,5 +79,9 @@ namespace MobiusEditor.RedAlert
|
||||
[DefaultValue(false)]
|
||||
public bool TruckCrate { get => truckCrate; set => SetField(ref truckCrate, value); }
|
||||
|
||||
private int newINIFormat;
|
||||
[DefaultValue(3)]
|
||||
public int NewINIFormat { get => newINIFormat; set => SetField(ref newINIFormat, value); }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace MobiusEditor.RedAlert
|
||||
|
||||
private static readonly Regex SinglePlayRegex = new Regex("^SC[A-LN-Z]\\d{2}[EWX][A-EL]$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private const string defVidVal = "<none>";
|
||||
private const string movieEmpty = "<none>";
|
||||
private const string RemarkOld = " (Classic only)";
|
||||
|
||||
private static readonly IEnumerable<string> movieTypesRemarksOld = new string[]
|
||||
@ -76,6 +76,7 @@ namespace MobiusEditor.RedAlert
|
||||
"RETALIATION_WINS",
|
||||
"RETALIATION_ANTS"
|
||||
};
|
||||
|
||||
private static readonly IEnumerable<string> movieTypesRa = new string[]
|
||||
{
|
||||
"AAGUN",
|
||||
@ -205,6 +206,8 @@ namespace MobiusEditor.RedAlert
|
||||
"RETALIATION_ANTS"
|
||||
};
|
||||
|
||||
private const string themeEmpty = "No Theme";
|
||||
|
||||
private static readonly IEnumerable<string> themeTypes = new string[]
|
||||
{
|
||||
"No Theme",
|
||||
@ -310,8 +313,8 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
// Remove any sections known and handled / disallowed by the editor.
|
||||
ini.Sections.Remove("Digest");
|
||||
ini.Sections.Remove("Basic");
|
||||
ini.Sections.Remove("Map");
|
||||
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");
|
||||
@ -330,7 +333,7 @@ namespace MobiusEditor.RedAlert
|
||||
ini.Sections.Remove("Briefing");
|
||||
foreach (var house in Map.Houses)
|
||||
{
|
||||
ini.Sections.Remove(house.Type.Name);
|
||||
INITools.ClearDataFrom(ini, house.Type.Name, (House)house);
|
||||
}
|
||||
extraSections = ini.Sections.Count == 0 ? null : ini.Sections;
|
||||
IEnumerable<string> errors = UpdateBuildingRules(ini, this.Map);
|
||||
@ -342,7 +345,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
public static bool CheckForRAMap(INI contents)
|
||||
{
|
||||
return GeneralUtils.CheckForIniInfo(contents, "MapPack");
|
||||
return INITools.CheckForIniInfo(contents, "MapPack");
|
||||
}
|
||||
|
||||
static GamePlugin()
|
||||
@ -364,10 +367,10 @@ namespace MobiusEditor.RedAlert
|
||||
var movies = new List<string>(movieTypesRa);
|
||||
for (int i = 0; i < movies.Count; ++i)
|
||||
{
|
||||
string vidName = GeneralUtils.AddRemarks(movies[i], defVidVal, true, movieTypesRemarksOld, RemarkOld);
|
||||
movies[i] = GeneralUtils.AddRemarks(vidName, defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
string vidName = GeneralUtils.AddRemarks(movies[i], movieEmpty, true, movieTypesRemarksOld, RemarkOld);
|
||||
movies[i] = GeneralUtils.AddRemarks(vidName, movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
}
|
||||
movies.Insert(0, defVidVal);
|
||||
movies.Insert(0, movieEmpty);
|
||||
movieTypes = movies.ToArray();
|
||||
var basicSection = new BasicSection();
|
||||
basicSection.SetDefault();
|
||||
@ -418,8 +421,10 @@ namespace MobiusEditor.RedAlert
|
||||
TerrainTypes.GetTypes(), OverlayTypes.GetTypes(), SmudgeTypes.GetTypes(Globals.ConvertCraters),
|
||||
EventTypes.GetTypes(), cellEventTypes, unitEventTypes, structureEventTypes, terrainEventTypes,
|
||||
ActionTypes.GetTypes(), cellActionTypes, unitActionTypes, structureActionTypes, terrainActionTypes,
|
||||
MissionTypes.GetTypes(), DirectionTypes.GetMainTypes(), DirectionTypes.GetAllTypes(), InfantryTypes.GetTypes(), UnitTypes.GetTypes(Globals.DisableAirUnits),
|
||||
BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(), fullTechnoTypes, waypoints, movieTypes, themeTypes)
|
||||
MissionTypes.GetTypes(), MissionTypes.MISSION_GUARD, MissionTypes.MISSION_STOP, MissionTypes.MISSION_HARVEST,
|
||||
MissionTypes.MISSION_UNLOAD, DirectionTypes.GetMainTypes(), DirectionTypes.GetAllTypes(), InfantryTypes.GetTypes(),
|
||||
UnitTypes.GetTypes(Globals.DisableAirUnits), BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(),
|
||||
fullTechnoTypes, waypoints, movieTypes, movieEmpty, themeTypes, themeEmpty)
|
||||
{
|
||||
TiberiumOrGoldValue = 35,
|
||||
GemValue = 110
|
||||
@ -466,10 +471,6 @@ namespace MobiusEditor.RedAlert
|
||||
var ini = new INI();
|
||||
iniBytes = File.ReadAllBytes(path);
|
||||
ParseIniContent(ini, iniBytes);
|
||||
using (var reader = new StreamReader(path))
|
||||
{
|
||||
ini.Parse(reader);
|
||||
}
|
||||
forceSingle = SinglePlayRegex.IsMatch(Path.GetFileNameWithoutExtension(path));
|
||||
errors.AddRange(LoadINI(ini, forceSingle, ref modified));
|
||||
}
|
||||
@ -511,9 +512,9 @@ namespace MobiusEditor.RedAlert
|
||||
|
||||
private void ParseIniContent(INI ini, Byte[] iniBytes)
|
||||
{
|
||||
Encoding encUtf8 = new UTF8Encoding(false, false);
|
||||
Encoding encDOS = Encoding.GetEncoding(437);
|
||||
String iniText = encDOS.GetString(iniBytes);
|
||||
Encoding encUtf8 = new UTF8Encoding(false, false);
|
||||
String iniTextUtf8 = encUtf8.GetString(iniBytes);
|
||||
ini.Parse(iniText);
|
||||
// Specific support for DOS-437 file but with some specific sections in UTF-8.
|
||||
@ -522,34 +523,34 @@ namespace MobiusEditor.RedAlert
|
||||
INI utf8Ini = new INI();
|
||||
utf8Ini.Parse(iniTextUtf8);
|
||||
// Steam section
|
||||
INISection steamSectionUtf = utf8Ini.Sections["Steam"];
|
||||
if (steamSectionUtf != null)
|
||||
INISection steamSectionUtf8 = utf8Ini.Sections["Steam"];
|
||||
if (steamSectionUtf8 != null)
|
||||
{
|
||||
if (!ini.Sections.Replace(steamSectionUtf))
|
||||
if (!ini.Sections.Replace(steamSectionUtf8))
|
||||
{
|
||||
ini.Sections.Add(steamSectionUtf);
|
||||
ini.Sections.Add(steamSectionUtf8);
|
||||
}
|
||||
}
|
||||
// Name and author from Basic section
|
||||
INISection basicSectionUtf = utf8Ini.Sections["Basic"];
|
||||
INISection basicSectionUtf8 = utf8Ini.Sections["Basic"];
|
||||
INISection basicSectionDos = ini.Sections["Basic"];
|
||||
if (basicSectionUtf != null && basicSectionDos != null)
|
||||
if (basicSectionUtf8 != null && basicSectionDos != null)
|
||||
{
|
||||
if (basicSectionUtf.Keys.Contains("Name") && !basicSectionUtf.Keys["Name"].Contains('\uFFFD'))
|
||||
if (basicSectionUtf8.Keys.Contains("Name") && !basicSectionUtf8.Keys["Name"].Contains('\uFFFD'))
|
||||
{
|
||||
basicSectionDos.Keys["Name"] = basicSectionUtf.Keys["Name"];
|
||||
basicSectionDos.Keys["Name"] = basicSectionUtf8.Keys["Name"];
|
||||
}
|
||||
if (basicSectionUtf.Keys.Contains("Author") && !basicSectionUtf.Keys["Author"].Contains('\uFFFD'))
|
||||
if (basicSectionUtf8.Keys.Contains("Author") && !basicSectionUtf8.Keys["Author"].Contains('\uFFFD'))
|
||||
{
|
||||
basicSectionDos.Keys["Author"] = basicSectionUtf.Keys["Author"];
|
||||
basicSectionDos.Keys["Author"] = basicSectionUtf8.Keys["Author"];
|
||||
}
|
||||
}
|
||||
// Remastered one-line "Text" briefing from [Briefing] section
|
||||
INISection briefSectionUtf = utf8Ini.Sections["Briefing"];
|
||||
INISection briefSectionUtf8 = utf8Ini.Sections["Briefing"];
|
||||
INISection briefSectionDos = ini.Sections["Briefing"];
|
||||
if (briefSectionUtf != null && briefSectionDos != null && briefSectionUtf.Keys.Contains("Text"))
|
||||
if (briefSectionUtf8 != null && briefSectionDos != null && briefSectionUtf8.Keys.Contains("Text"))
|
||||
{
|
||||
briefSectionDos.Keys["Text"] = briefSectionUtf.Keys["Text"];
|
||||
briefSectionDos.Keys["Text"] = briefSectionUtf8.Keys["Text"];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -563,27 +564,27 @@ namespace MobiusEditor.RedAlert
|
||||
// Just gonna remove this; I assume it'll be invalid after a re-save anyway.
|
||||
ini.Sections.Extract("Digest");
|
||||
// Basic info
|
||||
var basicSection = ini.Sections.Extract("Basic");
|
||||
BasicSection basic = (BasicSection)Map.BasicSection;
|
||||
INISection basicSection = INITools.ParseAndLeaveRemainder(ini, "Basic", basic, new MapContext(Map, true));
|
||||
if (basicSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, true), basicSection, Map.BasicSection);
|
||||
Model.BasicSection basic = Map.BasicSection;
|
||||
basic.Intro = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Intro, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Intro = GeneralUtils.FilterToExisting(basic.Intro, defVidVal, true, movieTypesRa);
|
||||
basic.Brief = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Brief, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Brief = GeneralUtils.FilterToExisting(basic.Brief, defVidVal, true, movieTypesRa);
|
||||
basic.Action = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Action, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Action = GeneralUtils.FilterToExisting(basic.Action, defVidVal, true, movieTypesRa);
|
||||
basic.Win = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win = GeneralUtils.FilterToExisting(basic.Win, defVidVal, true, movieTypesRa);
|
||||
basic.Win2 = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win2, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win2 = GeneralUtils.FilterToExisting(basic.Win2, defVidVal, true, movieTypesRa);
|
||||
basic.Win3 = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win3, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win3 = GeneralUtils.FilterToExisting(basic.Win3, defVidVal, true, movieTypesRa);
|
||||
basic.Win4 = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win4, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win4 = GeneralUtils.FilterToExisting(basic.Win4, defVidVal, true, movieTypesRa);
|
||||
basic.Lose = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Lose, defVidVal, true, movieTypesRemarksOld, RemarkOld), defVidVal, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Lose = GeneralUtils.FilterToExisting(basic.Lose, defVidVal, true, movieTypesRa);
|
||||
|
||||
basic.Intro = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Intro, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Intro = GeneralUtils.FilterToExisting(basic.Intro, movieEmpty, true, movieTypesRa);
|
||||
basic.Brief = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Brief, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Brief = GeneralUtils.FilterToExisting(basic.Brief, movieEmpty, true, movieTypesRa);
|
||||
basic.Action = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Action, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Action = GeneralUtils.FilterToExisting(basic.Action, movieEmpty, true, movieTypesRa);
|
||||
basic.Win = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win = GeneralUtils.FilterToExisting(basic.Win, movieEmpty, true, movieTypesRa);
|
||||
basic.Win2 = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win2, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win2 = GeneralUtils.FilterToExisting(basic.Win2, movieEmpty, true, movieTypesRa);
|
||||
basic.Win3 = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win3, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win3 = GeneralUtils.FilterToExisting(basic.Win3, movieEmpty, true, movieTypesRa);
|
||||
basic.Win4 = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Win4, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Win4 = GeneralUtils.FilterToExisting(basic.Win4, movieEmpty, true, movieTypesRa);
|
||||
basic.Lose = GeneralUtils.AddRemarks(GeneralUtils.AddRemarks(basic.Lose, movieEmpty, true, movieTypesRemarksOld, RemarkOld), movieEmpty, true, movieTypesRemarksNew, RemarkNew);
|
||||
basic.Lose = GeneralUtils.FilterToExisting(basic.Lose, movieEmpty, true, movieTypesRa);
|
||||
}
|
||||
String plName = Map.BasicSection.Player;
|
||||
HouseType player = Map.HouseTypes.Where(t => t.Equals(plName)).FirstOrDefault() ?? Map.HouseTypes.First();
|
||||
@ -605,11 +606,7 @@ namespace MobiusEditor.RedAlert
|
||||
// Needs to be enabled in advance; it determines which units are valid to have placed on the map.
|
||||
Map.BasicSection.ExpansionEnabled = aftermathEnabled;
|
||||
// Map info
|
||||
var mapSection = ini.Sections.Extract("Map");
|
||||
if (mapSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, true), mapSection, Map.MapSection);
|
||||
}
|
||||
INISection mapSection = INITools.ParseAndLeaveRemainder(ini, "Map", Map.MapSection, new MapContext(Map, true));
|
||||
Map.MapSection.FixBounds();
|
||||
#if DEBUG
|
||||
//MessageBox.Show("Graphics loaded");
|
||||
@ -720,7 +717,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
var trigger = new Trigger { Name = Key };
|
||||
trigger.PersistentType = (TriggerPersistentType)int.Parse(tokens[0]);
|
||||
trigger.House = Map.HouseTypes.Where(t => t.Equals(sbyte.Parse(tokens[1]))).FirstOrDefault()?.Name ?? "None";
|
||||
trigger.House = Map.HouseTypes.Where(t => t.Equals(sbyte.Parse(tokens[1]))).FirstOrDefault()?.Name ?? House.None;
|
||||
trigger.EventControl = (TriggerMultiStyleType)int.Parse(tokens[2]);
|
||||
trigger.Event1.EventType = indexToType(Map.EventTypes, tokens[4]);
|
||||
trigger.Event1.Team = tokens[5];
|
||||
@ -819,12 +816,12 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
}
|
||||
//MessageBox.Show("at triggers");
|
||||
HashSet<string> checkTrigs = Trigger.None.Yield().Concat(triggers.Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkCellTrigs = Map.FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkUnitTrigs = Trigger.None.Yield().Concat(Map.FilterUnitTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkStrcTrigs = Trigger.None.Yield().Concat(Map.FilterStructureTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkTrigs = Trigger.None.Yield().Concat(triggers.Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkCellTrigs = Map.FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkUnitTrigs = Trigger.None.Yield().Concat(Map.FilterUnitTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkStrcTrigs = Trigger.None.Yield().Concat(Map.FilterStructureTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
// Terrain objects in RA have no triggers
|
||||
//HashSet<string> checkTerrTrigs = Trigger.None.Yield().Concat(Map.FilterTerrainTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
//HashSet<string> checkTerrTrigs = Trigger.None.Yield().Concat(Map.FilterTerrainTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
//MessageBox.Show("MapPack");
|
||||
var mapPackSection = ini.Sections.Extract("MapPack");
|
||||
if (mapPackSection != null)
|
||||
@ -1921,16 +1918,9 @@ namespace MobiusEditor.RedAlert
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var houseSection = ini.Sections.Extract(house.Type.Name);
|
||||
if (houseSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, true), houseSection, house);
|
||||
house.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
house.Enabled = false;
|
||||
}
|
||||
House gameHouse = (House)house;
|
||||
INISection houseSection = INITools.ParseAndLeaveRemainder(ini, gameHouse.Type.Name, gameHouse, new MapContext(Map, true));
|
||||
house.Enabled = houseSection != null;
|
||||
}
|
||||
string indexToName<T>(IList<T> list, string index, string defaultValue) where T : INamedType
|
||||
{
|
||||
@ -1975,7 +1965,7 @@ namespace MobiusEditor.RedAlert
|
||||
Map.Triggers.AddRange(triggers);
|
||||
extraSections = ini.Sections;
|
||||
bool switchedToSolo = false;
|
||||
if (forceSoloMission)
|
||||
if (forceSoloMission && !basic.SoloMission)
|
||||
{
|
||||
int playerId = player.ID;
|
||||
bool hasWinTrigger =
|
||||
@ -2002,7 +1992,7 @@ namespace MobiusEditor.RedAlert
|
||||
private IEnumerable<string> UpdateBuildingRules(INI ini, Map map)
|
||||
{
|
||||
List<string> errors = new List<string>();
|
||||
Dictionary<string, BuildingType> originals = BuildingTypes.GetTypes().ToDictionary(b => b.Name, StringComparer.InvariantCultureIgnoreCase);
|
||||
Dictionary<string, BuildingType> originals = BuildingTypes.GetTypes().ToDictionary(b => b.Name, StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<Point> refreshPoints = new HashSet<Point>();
|
||||
List<(Point Location, Building Occupier)> buildings = map.Buildings.OfType<Building>()
|
||||
.OrderBy(pb => pb.Location.Y * map.Metrics.Width + pb.Location.X).ToList();
|
||||
@ -2209,7 +2199,7 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
}
|
||||
}
|
||||
Model.BasicSection basic = Map.BasicSection;
|
||||
BasicSection basic = (BasicSection)Map.BasicSection;
|
||||
// Make new Aftermath section
|
||||
INISection newAftermathSection = new INISection("Aftermath");
|
||||
newAftermathSection["NewUnitsEnabled"] = basic.ExpansionEnabled ? "1" : "0";
|
||||
@ -2250,14 +2240,13 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
basic.Name = String.Join(" ", name);
|
||||
}
|
||||
INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Basic"), Map.BasicSection);
|
||||
INITools.FillAndReAdd(ini, "Basic", basic, new MapContext(Map, false), true);
|
||||
Map.MapSection.FixBounds();
|
||||
INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Map"), Map.MapSection);
|
||||
INITools.FillAndReAdd(ini, "Map", Map.MapSection, new MapContext(Map, false), true);
|
||||
if (fileType != FileType.PGM)
|
||||
{
|
||||
INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Steam"), Map.SteamSection);
|
||||
}
|
||||
ini["Basic"]["NewINIFormat"] = "3";
|
||||
var smudgeSection = ini.Sections.Add("SMUDGE");
|
||||
// Flatten multi-cell bibs
|
||||
Dictionary<int, Smudge> resolvedSmudge = new Dictionary<int, Smudge>();
|
||||
@ -2441,7 +2430,6 @@ namespace MobiusEditor.RedAlert
|
||||
ship.Trigger
|
||||
);
|
||||
}
|
||||
|
||||
var triggersSection = ini.Sections.Add("Trigs");
|
||||
foreach (var trigger in Map.Triggers)
|
||||
{
|
||||
@ -2477,7 +2465,6 @@ namespace MobiusEditor.RedAlert
|
||||
|
||||
triggersSection[trigger.Name] = string.Join(",", tokens);
|
||||
}
|
||||
|
||||
var waypointsSection = ini.Sections.Add("Waypoints");
|
||||
for (var i = 0; i < Map.Waypoints.Length; ++i)
|
||||
{
|
||||
@ -2487,23 +2474,22 @@ namespace MobiusEditor.RedAlert
|
||||
waypointsSection[i.ToString()] = waypoint.Cell.Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var house in Map.Houses)
|
||||
{
|
||||
if ((house.Type.ID < 0) || !house.Enabled)
|
||||
if (house.Type.ID < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
INI.WriteSection(new MapContext(Map, true), ini.Sections.Add(house.Type.Name), house);
|
||||
House gameHouse = (House)house;
|
||||
bool enabled = house.Enabled;
|
||||
INITools.FillAndReAdd(ini, gameHouse.Type.Name, gameHouse, new MapContext(Map, false), enabled);
|
||||
}
|
||||
|
||||
ini.Sections.Remove("Briefing");
|
||||
if (!string.IsNullOrEmpty(Map.BriefingSection.Briefing))
|
||||
{
|
||||
//var briefingSection = SaveIniBriefing(ini);
|
||||
SaveIniBriefing(ini);
|
||||
}
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
@ -2540,11 +2526,9 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ini.Sections.Remove("MapPack");
|
||||
CompressLCWSection(ini.Sections.Add("MapPack"), stream.ToArray());
|
||||
}
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
|
@ -18,33 +18,57 @@ namespace MobiusEditor.RedAlert
|
||||
{
|
||||
public static class MissionTypes
|
||||
{
|
||||
public const string MISSION_SLEEP = "Sleep";
|
||||
public const string MISSION_ATTACK = "Attack";
|
||||
public const string MISSION_MOVE = "Move";
|
||||
public const string MISSION_QMOVE = "QMove";
|
||||
public const string MISSION_RETREAT = "Retreat";
|
||||
public const string MISSION_STICKY = "Sticky";
|
||||
public const string MISSION_GUARD = "Guard";
|
||||
public const string MISSION_ENTER = "Enter";
|
||||
public const string MISSION_CAPTURE = "Capture";
|
||||
public const string MISSION_HARVEST = "Harvest";
|
||||
public const string MISSION_AREAGUARD = "Area Guard";
|
||||
public const string MISSION_RETURN = "Return";
|
||||
public const string MISSION_STOP = "Stop";
|
||||
public const string MISSION_AMBUSH = "Ambush";
|
||||
public const string MISSION_HUNT = "Hunt";
|
||||
public const string MISSION_UNLOAD = "Unload";
|
||||
public const string MISSION_SABOTAGE = "Sabotage";
|
||||
public const string MISSION_CONSTRUCTION = "Construction";
|
||||
public const string MISSION_SELLING = "Selling";
|
||||
public const string MISSION_REPAIR = "Repair";
|
||||
public const string MISSION_RESCUE = "Rescue";
|
||||
public const string MISSION_MISSILE = "Missile";
|
||||
public const string MISSION_HARMLESS = "Harmless";
|
||||
|
||||
private static readonly string[] Types = new string[]
|
||||
{
|
||||
// Nyerguds upgrade: Removed irrelevant types for preplaced units.
|
||||
// Note that TeamTypes use a separate list, defined in the TeamMissionTypes class.
|
||||
"Sleep",
|
||||
//"Attack",
|
||||
//"Move",
|
||||
//"QMove",
|
||||
//"Retreat",
|
||||
"Sticky",
|
||||
"Guard",
|
||||
//"Enter",
|
||||
//"Capture",
|
||||
"Harvest",
|
||||
"Area Guard",
|
||||
"Return",
|
||||
"Stop",
|
||||
//"Ambush",
|
||||
"Hunt",
|
||||
"Unload",
|
||||
//"Sabotage",
|
||||
//"Construction",
|
||||
//"Selling",
|
||||
//"Repair",
|
||||
//"Rescue",
|
||||
//"Missile",
|
||||
"Harmless"
|
||||
MISSION_SLEEP,
|
||||
//MISSION_ATTACK,
|
||||
//MISSION_MOVE,
|
||||
//MISSION_QMOVE,
|
||||
//MISSION_RETREAT,
|
||||
MISSION_STICKY,
|
||||
MISSION_GUARD,
|
||||
//MISSION_ENTER,
|
||||
//MISSION_CAPTURE,
|
||||
MISSION_HARVEST,
|
||||
MISSION_AREAGUARD,
|
||||
MISSION_RETURN,
|
||||
MISSION_STOP,
|
||||
MISSION_AMBUSH,
|
||||
MISSION_HUNT,
|
||||
MISSION_UNLOAD,
|
||||
//MISSION_SABOTAGE,
|
||||
//MISSION_CONSTRUCTION,
|
||||
//MISSION_SELLING,
|
||||
//MISSION_REPAIR,
|
||||
//MISSION_RESCUE,
|
||||
//MISSION_MISSILE,
|
||||
MISSION_HARMLESS,
|
||||
};
|
||||
|
||||
public static IEnumerable<string> GetTypes()
|
||||
|
@ -1378,10 +1378,13 @@ namespace MobiusEditor.Render
|
||||
{
|
||||
float borderSize = Math.Max(0.5f, tileSize.Width / 60.0f);
|
||||
float thickBorderSize = Math.Max(1f, tileSize.Width / 20.0f);
|
||||
HashSet<String> specifiedSet = new HashSet<String>(specified, StringComparer.InvariantCultureIgnoreCase);
|
||||
using (var cellTriggersBackgroundBrush = new SolidBrush(Color.FromArgb(96, fillColor)))
|
||||
using (var cellTriggersBrush = new SolidBrush(Color.FromArgb(128, textColor)))
|
||||
using (var cellTriggerPen = new Pen(borderColor, thickborder ? thickBorderSize : borderSize))
|
||||
HashSet<String> specifiedSet = new HashSet<String>(specified, StringComparer.OrdinalIgnoreCase);
|
||||
using (SolidBrush prevCellTriggersBackgroundBrush = new SolidBrush(Color.FromArgb(48, fillColor)))
|
||||
using (SolidBrush prevCellTriggersBrush = new SolidBrush(Color.FromArgb(64, textColor)))
|
||||
using (Pen prevCellTriggerPen = new Pen(Color.FromArgb(128, borderColor), thickborder ? thickBorderSize : borderSize))
|
||||
using (SolidBrush cellTriggersBackgroundBrush = new SolidBrush(Color.FromArgb(96, fillColor)))
|
||||
using (SolidBrush cellTriggersBrush = new SolidBrush(Color.FromArgb(128, textColor)))
|
||||
using (Pen cellTriggerPen = new Pen(borderColor, thickborder ? thickBorderSize : borderSize))
|
||||
{
|
||||
foreach (var (cell, cellTrigger) in map.CellTriggers)
|
||||
{
|
||||
@ -1394,8 +1397,9 @@ namespace MobiusEditor.Render
|
||||
var y = cell / map.Metrics.Width;
|
||||
var location = new Point(x * tileSize.Width, y * tileSize.Height);
|
||||
var textBounds = new Rectangle(location, tileSize);
|
||||
graphics.FillRectangle(cellTriggersBackgroundBrush, textBounds);
|
||||
graphics.DrawRectangle(cellTriggerPen, textBounds);
|
||||
bool isPreview = cellTrigger.Tint.A != 255;
|
||||
graphics.FillRectangle(isPreview ? prevCellTriggersBackgroundBrush : cellTriggersBackgroundBrush, textBounds);
|
||||
graphics.DrawRectangle(isPreview ? prevCellTriggerPen : cellTriggerPen, textBounds);
|
||||
StringFormat stringFormat = new StringFormat
|
||||
{
|
||||
Alignment = StringAlignment.Center,
|
||||
@ -1405,7 +1409,7 @@ namespace MobiusEditor.Render
|
||||
using (var font = graphics.GetAdjustedFont(text, SystemFonts.DefaultFont, textBounds.Width, textBounds.Height,
|
||||
Math.Max(1, (int)(24 * tileScale)), Math.Max(1, (int)(48 * tileScale)), stringFormat, true))
|
||||
{
|
||||
graphics.DrawString(text.ToString(), font, cellTriggersBrush, textBounds, stringFormat);
|
||||
graphics.DrawString(text.ToString(), font, isPreview ? prevCellTriggersBrush : cellTriggersBrush, textBounds, stringFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,8 @@ using MobiusEditor.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MobiusEditor.SoleSurvivor
|
||||
{
|
||||
@ -17,13 +14,34 @@ namespace MobiusEditor.SoleSurvivor
|
||||
|
||||
protected const int cratePoints = 4;
|
||||
protected const int teamStartPoints = 8;
|
||||
|
||||
protected static readonly IEnumerable<string> movieTypesSole = new string[]
|
||||
{
|
||||
"WESTLOGO",
|
||||
};
|
||||
|
||||
protected static readonly IEnumerable<string> themeTypesSole = new string[]
|
||||
{
|
||||
"No Theme",
|
||||
"WORKREMX",
|
||||
"CRSHNVOX",
|
||||
"DEPTHCHG",
|
||||
"DRILL",
|
||||
"HELLNVOX",
|
||||
"IRONFIST",
|
||||
"MERCY98",
|
||||
"MUDREMX",
|
||||
"CREEPING",
|
||||
"MAP1",
|
||||
};
|
||||
|
||||
public override String Name => "Sole Survivor";
|
||||
public override GameType GameType => GameType.SoleSurvivor;
|
||||
public override bool IsMegaMap => true;
|
||||
|
||||
public static bool CheckForSSmap(INI iniContents)
|
||||
{
|
||||
return GeneralUtils.CheckForIniInfo(iniContents, "Crates");
|
||||
return INITools.CheckForIniInfo(iniContents, "Crates");
|
||||
}
|
||||
|
||||
protected CratesSection cratesSection;
|
||||
@ -100,14 +118,19 @@ namespace MobiusEditor.SoleSurvivor
|
||||
flagColors[6] = Globals.TheTeamColorManager["MULTI2"];
|
||||
// Multi8: RA Purple
|
||||
flagColors[7] = new TeamColor(Globals.TheTeamColorManager, flagColors[0], "MULTI8", new Vector3(0.410f, 0.100f, 0.000f));
|
||||
List<String> movies = movieTypesTD.Concat(movieTypesSole).ToList();
|
||||
ExplorerComparer sorter = new ExplorerComparer();
|
||||
movies.Sort(sorter);
|
||||
Size mapSize = !megaMap ? TiberianDawn.Constants.MaxSize : TiberianDawn.Constants.MaxSizeMega;
|
||||
Map = new Map(basicSection, null, mapSize, typeof(House), houseTypes,
|
||||
flagColors, TiberianDawn.TheaterTypes.GetTypes(), TiberianDawn.TemplateTypes.GetTypes(),
|
||||
TiberianDawn.TerrainTypes.GetTypes(), OverlayTypes.GetTypes(), TiberianDawn.SmudgeTypes.GetTypes(Globals.ConvertCraters),
|
||||
TiberianDawn.EventTypes.GetTypes(), cellEventTypes, unitEventTypes, structureEventTypes, terrainEventTypes,
|
||||
TiberianDawn.ActionTypes.GetTypes(), cellActionTypes, unitActionTypes, structureActionTypes, terrainActionTypes,
|
||||
TiberianDawn.MissionTypes.GetTypes(), DirectionTypes.GetMainTypes(), DirectionTypes.GetAllTypes(), infantry, units,
|
||||
buildings, TiberianDawn.TeamMissionTypes.GetTypes(), fullTechnoTypes, waypoints, movieTypes, themeTypes)
|
||||
TiberianDawn.MissionTypes.GetTypes(), TiberianDawn.MissionTypes.MISSION_GUARD, TiberianDawn.MissionTypes.MISSION_STOP,
|
||||
TiberianDawn.MissionTypes.MISSION_HARVEST, TiberianDawn.MissionTypes.MISSION_UNLOAD, DirectionTypes.GetMainTypes(),
|
||||
DirectionTypes.GetAllTypes(), infantry, units, buildings, TiberianDawn.TeamMissionTypes.GetTypes(), fullTechnoTypes,
|
||||
waypoints, movies, movieEmpty, themeTypesSole, themeEmpty)
|
||||
{
|
||||
TiberiumOrGoldValue = 25
|
||||
};
|
||||
@ -137,7 +160,14 @@ namespace MobiusEditor.SoleSurvivor
|
||||
var cratesIniSection = extraSections.Extract("Crates");
|
||||
if (cratesIniSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, false), cratesIniSection, this.cratesSection);
|
||||
try
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, false), cratesIniSection, this.cratesSection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add("Parsing of [Crates] section failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
@ -192,7 +222,7 @@ namespace MobiusEditor.SoleSurvivor
|
||||
public override HashSet<string> GetHousesWithProduction()
|
||||
{
|
||||
// Not applicable. Return empty set.
|
||||
return new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,125 @@ namespace MobiusEditor.TiberianDawn
|
||||
|
||||
protected static readonly IEnumerable<ITechnoType> fullTechnoTypes;
|
||||
|
||||
protected const string defVidVal = "x";
|
||||
protected const string movieEmpty = "x";
|
||||
protected readonly IEnumerable<string> movieTypes;
|
||||
|
||||
protected static readonly IEnumerable<string> movieTypesTD = new string[]
|
||||
{
|
||||
"AIRSTRK",
|
||||
"AKIRA",
|
||||
"BANNER",
|
||||
"BANR_NOD",
|
||||
"BCANYON",
|
||||
"BKGROUND",
|
||||
"BLACKOUT",
|
||||
"BODYBAGS",
|
||||
"BOMBAWAY",
|
||||
"BOMBFLEE",
|
||||
"BURDET1",
|
||||
"BURDET2",
|
||||
"CC2TEASE",
|
||||
"CONSYARD",
|
||||
"DESFLEES",
|
||||
"DESKILL",
|
||||
"DESOLAT",
|
||||
"DESSWEEP",
|
||||
"DINO",
|
||||
"FLAG",
|
||||
"FLYY",
|
||||
"FORESTKL",
|
||||
"GAMEOVER",
|
||||
"GDI1",
|
||||
"GDI2",
|
||||
"GDI3",
|
||||
"GDI4A",
|
||||
"GDI4B",
|
||||
"GDI5",
|
||||
"GDI6",
|
||||
"GDI7",
|
||||
"GDI8A",
|
||||
"GDI8B",
|
||||
"GDI9",
|
||||
"GDI10",
|
||||
"GDI11",
|
||||
"GDI12",
|
||||
"GDI13",
|
||||
"GDI14",
|
||||
"GDI15",
|
||||
"GDI3LOSE",
|
||||
"GDIEND1",
|
||||
"GDIEND2",
|
||||
"GDIFINA",
|
||||
"GDIFINB",
|
||||
"GDILOSE",
|
||||
"GENERIC",
|
||||
"GUNBOAT",
|
||||
"HELLVALY",
|
||||
"INFERNO",
|
||||
"INSITES",
|
||||
"INTRO2",
|
||||
"IONTEST",
|
||||
"KANEPRE",
|
||||
"LANDING",
|
||||
"LOGO",
|
||||
"NAPALM",
|
||||
"NITEJUMP",
|
||||
"NOD1",
|
||||
"NOD2",
|
||||
"NOD3",
|
||||
"NOD4A",
|
||||
"NOD4B",
|
||||
"NOD5",
|
||||
"NOD6",
|
||||
"NOD7A",
|
||||
"NOD7B",
|
||||
"NOD8",
|
||||
"NOD9",
|
||||
"NOD10A",
|
||||
"NOD10B",
|
||||
"NOD11",
|
||||
"NOD12",
|
||||
"NOD13",
|
||||
"NOD1PRE",
|
||||
"NODEND1",
|
||||
"NODEND2",
|
||||
"NODEND3",
|
||||
"NODEND4",
|
||||
"NODFINAL",
|
||||
"NODFLEES",
|
||||
"NODLOSE",
|
||||
"NODSWEEP",
|
||||
"NUKE",
|
||||
"OBEL",
|
||||
"PARATROP",
|
||||
"PINTLE",
|
||||
"PLANECRA",
|
||||
"PODIUM",
|
||||
"REFINT",
|
||||
"REFINERY",
|
||||
"RETRO",
|
||||
"SABOTAGE",
|
||||
"SAMDIE",
|
||||
"SAMSITE",
|
||||
"SEIGE",
|
||||
"SETHPRE",
|
||||
"SIZZLE",
|
||||
"SIZZLE2",
|
||||
"SPYCRASH",
|
||||
"STEALTH",
|
||||
"SUNDIAL",
|
||||
"TANKGO",
|
||||
"TANKKILL",
|
||||
"TBRINFO1",
|
||||
"TBRINFO2",
|
||||
"TBRINFO3",
|
||||
"TIBERFX",
|
||||
"TRAILER",
|
||||
"TRTKIL_D",
|
||||
"TURTKILL",
|
||||
"VISOR",
|
||||
};
|
||||
|
||||
protected static readonly IEnumerable<string> movieTypesAdditional = new string[]
|
||||
{
|
||||
"BODYBAGS (Classic only)",
|
||||
@ -54,6 +170,8 @@ namespace MobiusEditor.TiberianDawn
|
||||
"TRTKIL_D (Classic only)",
|
||||
};
|
||||
|
||||
protected const string themeEmpty = "No Theme";
|
||||
|
||||
protected static readonly IEnumerable<string> themeTypes = new string[]
|
||||
{
|
||||
"No Theme",
|
||||
@ -98,6 +216,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
"NOD_MAP1",
|
||||
"OUTTAKES"
|
||||
};
|
||||
|
||||
public virtual string Name => "Tiberian Dawn";
|
||||
|
||||
public virtual GameType GameType => GameType.TiberianDawn;
|
||||
@ -145,8 +264,8 @@ namespace MobiusEditor.TiberianDawn
|
||||
return;
|
||||
}
|
||||
// Remove any sections known and handled / disallowed by the editor.
|
||||
ini.Sections.Remove("Basic");
|
||||
ini.Sections.Remove("Map");
|
||||
INITools.ClearDataFrom(ini, "Basic", (BasicSection)Map.BasicSection);
|
||||
INITools.ClearDataFrom(ini, "Map", Map.MapSection);
|
||||
ini.Sections.Remove("Briefing");
|
||||
ini.Sections.Remove("Steam");
|
||||
ini.Sections.Remove("TeamTypes");
|
||||
@ -163,7 +282,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
ini.Sections.Remove("CellTriggers");
|
||||
foreach (var house in Map.Houses)
|
||||
{
|
||||
ini.Sections.Remove(house.Type.Name);
|
||||
INITools.ClearDataFrom(ini, house.Type.Name, (House)house);
|
||||
}
|
||||
extraSections = ini.Sections.Count == 0 ? null : ini.Sections;
|
||||
}
|
||||
@ -171,7 +290,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
|
||||
public static bool CheckForMegamap(INI iniContents)
|
||||
{
|
||||
return GeneralUtils.CheckForIniInfo(iniContents, "Map", "Version", "1");
|
||||
return INITools.CheckForIniInfo(iniContents, "Map", "Version", "1");
|
||||
}
|
||||
|
||||
static GamePlugin()
|
||||
@ -194,10 +313,22 @@ namespace MobiusEditor.TiberianDawn
|
||||
}
|
||||
}
|
||||
}
|
||||
movies.AddRange(movieTypesAdditional);
|
||||
// Preparation for decoupling from remaster files.
|
||||
if (movies.Count == 0)
|
||||
{
|
||||
movies.AddRange(movieTypesTD);
|
||||
}
|
||||
foreach (string mov in movieTypesAdditional)
|
||||
{
|
||||
string movName = GeneralUtils.TrimRemarks(mov, true, ';', '(');
|
||||
if (movies.FirstOrDefault(m => m.Equals(movName, StringComparison.OrdinalIgnoreCase)) == null)
|
||||
{
|
||||
movies.Add(mov);
|
||||
}
|
||||
}
|
||||
movies = movies.Distinct().ToList();
|
||||
movies.Sort(new ExplorerComparer());
|
||||
movies.Insert(0, defVidVal);
|
||||
movies.Insert(0, movieEmpty);
|
||||
movieTypes = movies.ToArray();
|
||||
}
|
||||
|
||||
@ -263,8 +394,10 @@ namespace MobiusEditor.TiberianDawn
|
||||
TerrainTypes.GetTypes(), OverlayTypes.GetTypes(), SmudgeTypes.GetTypes(Globals.ConvertCraters),
|
||||
EventTypes.GetTypes(), cellEventTypes, unitEventTypes, structureEventTypes, terrainEventTypes,
|
||||
ActionTypes.GetTypes(), cellActionTypes, unitActionTypes, structureActionTypes, terrainActionTypes,
|
||||
MissionTypes.GetTypes(), DirectionTypes.GetMainTypes(), DirectionTypes.GetAllTypes(), InfantryTypes.GetTypes(), UnitTypes.GetTypes(Globals.DisableAirUnits),
|
||||
BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(), fullTechnoTypes, waypoints, movieTypes, themeTypes)
|
||||
MissionTypes.GetTypes(), MissionTypes.MISSION_GUARD, MissionTypes.MISSION_STOP, MissionTypes.MISSION_HARVEST,
|
||||
MissionTypes.MISSION_UNLOAD, DirectionTypes.GetMainTypes(), DirectionTypes.GetAllTypes(), InfantryTypes.GetTypes(),
|
||||
UnitTypes.GetTypes(Globals.DisableAirUnits), BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(),
|
||||
fullTechnoTypes, waypoints, movieTypes, movieEmpty, themeTypes, themeEmpty)
|
||||
{
|
||||
TiberiumOrGoldValue = 25
|
||||
};
|
||||
@ -377,9 +510,9 @@ namespace MobiusEditor.TiberianDawn
|
||||
|
||||
private void ParseIniContent(INI ini, Byte[] iniBytes, Boolean forSole)
|
||||
{
|
||||
Encoding encUtf8 = new UTF8Encoding(false, false);
|
||||
Encoding encDOS = Encoding.GetEncoding(437);
|
||||
String iniText = encDOS.GetString(iniBytes);
|
||||
Encoding encUtf8 = new UTF8Encoding(false, false);
|
||||
String iniTextUtf8 = encUtf8.GetString(iniBytes);
|
||||
if (!forSole)
|
||||
{
|
||||
@ -480,7 +613,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
}
|
||||
// Only process the ini if any of the detected lines have a found amount of more than one. If references to literal ROAD2 are found,
|
||||
// also process the ini so they can be removed; we do not want those to be accepted as valid type by the editor.
|
||||
if (foundAmounts.All(k => k.Value == 1) && !cellTypes.Values.Contains(OverlayTypes.Road2.Name, StringComparer.InvariantCultureIgnoreCase))
|
||||
if (foundAmounts.All(k => k.Value == 1) && !cellTypes.Values.Contains(OverlayTypes.Road2.Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return iniText;
|
||||
}
|
||||
@ -549,29 +682,27 @@ namespace MobiusEditor.TiberianDawn
|
||||
{
|
||||
var errors = new List<string>();
|
||||
Map.BeginUpdate();
|
||||
var basicSection = ini.Sections.Extract("Basic");
|
||||
BasicSection basic = (BasicSection)Map.BasicSection;
|
||||
INISection basicSection = INITools.ParseAndLeaveRemainder(ini, "Basic", Map.BasicSection, new MapContext(Map, false));
|
||||
if (basicSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, false), basicSection, Map.BasicSection);
|
||||
char[] cutfrom = { ';', '(' };
|
||||
string[] toAddRem = movieTypesAdditional.Select(vid => GeneralUtils.TrimRemarks(vid, true, cutfrom)).ToArray();
|
||||
Model.BasicSection basic = Map.BasicSection;
|
||||
const string remark = " (Classic only)";
|
||||
basic.Intro = GeneralUtils.AddRemarks(basic.Intro, defVidVal, true, toAddRem, remark);
|
||||
basic.Brief = GeneralUtils.AddRemarks(basic.Brief, defVidVal, true, toAddRem, remark);
|
||||
basic.Action = GeneralUtils.AddRemarks(basic.Action, defVidVal, true, toAddRem, remark);
|
||||
basic.Win = GeneralUtils.AddRemarks(basic.Win, defVidVal, true, toAddRem, remark);
|
||||
basic.Win2 = GeneralUtils.AddRemarks(basic.Win2, defVidVal, true, toAddRem, remark);
|
||||
basic.Win3 = GeneralUtils.AddRemarks(basic.Win3, defVidVal, true, toAddRem, remark);
|
||||
basic.Win4 = GeneralUtils.AddRemarks(basic.Win4, defVidVal, true, toAddRem, remark);
|
||||
basic.Lose = GeneralUtils.AddRemarks(basic.Lose, defVidVal, true, toAddRem, remark);
|
||||
basic.Intro = GeneralUtils.AddRemarks(basic.Intro, movieEmpty, true, toAddRem, remark);
|
||||
basic.Brief = GeneralUtils.AddRemarks(basic.Brief, movieEmpty, true, toAddRem, remark);
|
||||
basic.Action = GeneralUtils.AddRemarks(basic.Action, movieEmpty, true, toAddRem, remark);
|
||||
basic.Win = GeneralUtils.AddRemarks(basic.Win, movieEmpty, true, toAddRem, remark);
|
||||
basic.Win2 = GeneralUtils.AddRemarks(basic.Win2, movieEmpty, true, toAddRem, remark);
|
||||
basic.Win3 = GeneralUtils.AddRemarks(basic.Win3, movieEmpty, true, toAddRem, remark);
|
||||
basic.Win4 = GeneralUtils.AddRemarks(basic.Win4, movieEmpty, true, toAddRem, remark);
|
||||
basic.Lose = GeneralUtils.AddRemarks(basic.Lose, movieEmpty, true, toAddRem, remark);
|
||||
}
|
||||
Map.BasicSection.Player = Map.HouseTypes.Where(t => t.Equals(Map.BasicSection.Player)).FirstOrDefault()?.Name ?? Map.HouseTypes.First().Name;
|
||||
var mapSection = ini.Sections.Extract("Map");
|
||||
if (mapSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, false), mapSection, Map.MapSection);
|
||||
}
|
||||
INISection mapSection = INITools.ParseAndLeaveRemainder(ini, "Map", Map.MapSection, new MapContext(Map, false));
|
||||
// Also clear megamap indicator.
|
||||
if (mapSection.Keys.Remove("Version") && mapSection.Keys.Count == 0)
|
||||
ini.Sections.Remove(mapSection.Name);
|
||||
Map.MapSection.FixBounds();
|
||||
#if DEBUG
|
||||
//MessageBox.Show("Graphics loaded");
|
||||
@ -591,7 +722,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
bool addSpace = false;
|
||||
while (briefingSection.Keys.Contains(lineStr = line.ToString()))
|
||||
{
|
||||
String briefLine = briefingSection[lineStr].Trim();
|
||||
String briefLine = briefingSection[lineStr].TrimStart();
|
||||
// C&C95 v1.06 line break format.
|
||||
bool hasBreak = briefLine.EndsWith("##");
|
||||
if (hasBreak)
|
||||
@ -602,7 +733,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
{
|
||||
briefLines.Append(" ");
|
||||
}
|
||||
briefLines.Append(briefLine);
|
||||
briefLines.Append(briefLine.TrimEnd());
|
||||
if (hasBreak)
|
||||
{
|
||||
briefLines.AppendLine();
|
||||
@ -611,7 +742,6 @@ namespace MobiusEditor.TiberianDawn
|
||||
line++;
|
||||
}
|
||||
Map.BriefingSection.Briefing = briefLines.ToString();
|
||||
//Map.BriefingSection.Briefing = string.Join(" ", briefingSection.Keys.Select(k => k.Value)).Replace("@", Environment.NewLine);
|
||||
}
|
||||
}
|
||||
var steamSection = ini.Sections.Extract("Steam");
|
||||
@ -815,11 +945,11 @@ namespace MobiusEditor.TiberianDawn
|
||||
// Sort
|
||||
var comparer = new ExplorerComparer();
|
||||
triggers.Sort((x, y) => comparer.Compare(x.Name, y.Name));
|
||||
Dictionary<string, string> checkTrigs = Trigger.None.Yield().Concat(triggers.Select(t => t.Name)).ToDictionary(t => t, t => t, StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkCellTrigs = Map.FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkUnitTrigs = Trigger.None.Yield().Concat(Map.FilterUnitTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkStrcTrigs = Trigger.None.Yield().Concat(Map.FilterStructureTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
HashSet<string> checkTerrTrigs = Trigger.None.Yield().Concat(Map.FilterTerrainTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
Dictionary<string, string> checkTrigs = Trigger.None.Yield().Concat(triggers.Select(t => t.Name)).ToDictionary(t => t, t => t, StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkCellTrigs = Map.FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkUnitTrigs = Trigger.None.Yield().Concat(Map.FilterUnitTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkStrcTrigs = Trigger.None.Yield().Concat(Map.FilterStructureTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> checkTerrTrigs = Trigger.None.Yield().Concat(Map.FilterTerrainTriggers(triggers).Select(t => t.Name)).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
var smudgeSection = ini.Sections.Extract("Smudge");
|
||||
if (smudgeSection != null)
|
||||
{
|
||||
@ -1069,7 +1199,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
|
||||
Strength = strength,
|
||||
Direction = DirectionType.GetDirectionType(dirValue, Map.UnitDirectionTypes),
|
||||
Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault() ?? Map.GetDefaultMission(unitType),
|
||||
Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5], StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault() ?? Map.GetDefaultMission(unitType),
|
||||
};
|
||||
// "Rescue" and "Unload" both make the MCV deploy, but "Rescue" looks very strange in the editor, so we keep only one of them and convert the other.
|
||||
if (MissionTypes.MISSION_RESCUE.Equals(tokens[5], StringComparison.InvariantCultureIgnoreCase) && newUnit.Type.Equals(UnitTypes.MCV))
|
||||
@ -1685,20 +1815,9 @@ namespace MobiusEditor.TiberianDawn
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var houseSection = ini.Sections.Extract(house.Type.Name);
|
||||
if (houseSection != null)
|
||||
{
|
||||
INI.ParseSection(new MapContext(Map, false), houseSection, house);
|
||||
house.Enabled = true;
|
||||
string correctedEdge;
|
||||
if (!correctedEdges.TryGetValue(house.Edge, out correctedEdge))
|
||||
correctedEdge = defaultEdge;
|
||||
house.Edge = correctedEdge;
|
||||
}
|
||||
else
|
||||
{
|
||||
house.Enabled = false;
|
||||
}
|
||||
House gameHouse = (House)house;
|
||||
INISection houseSection = INITools.ParseAndLeaveRemainder(ini, gameHouse.Type.Name, gameHouse, new MapContext(Map, false));
|
||||
gameHouse.Enabled = houseSection != null;
|
||||
}
|
||||
UpdateBasePlayerHouse();
|
||||
errors.AddRange(CheckTriggers(triggers, true, true, false, out _, false, out _));
|
||||
@ -1707,7 +1826,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
Map.Triggers.AddRange(triggers);
|
||||
Map.TeamTypes.Sort((x, y) => comparer.Compare(x.Name, y.Name));
|
||||
extraSections = ini.Sections;
|
||||
bool switchedToSolo = !forSole && forceSoloMission && !Map.BasicSection.SoloMission
|
||||
bool switchedToSolo = !forSole && forceSoloMission && !basic.SoloMission
|
||||
&& ((triggers.Any(t => t.Action1.ActionType == ActionTypes.ACTION_WIN) && triggers.Any(t => t.Action1.ActionType == ActionTypes.ACTION_LOSE))
|
||||
|| triggers.Any(t => t.Event1.EventType == EventTypes.EVENT_ANY && t.Action1.ActionType == ActionTypes.ACTION_WINLOSE));
|
||||
if (switchedToSolo)
|
||||
@ -2009,7 +2128,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
|
||||
protected INISection SaveIniBasic(INI ini, string fileName)
|
||||
{
|
||||
Model.BasicSection basic = Map.BasicSection;
|
||||
BasicSection basic = (BasicSection)Map.BasicSection;
|
||||
char[] cutfrom = { ';', '(' };
|
||||
basic.Intro = GeneralUtils.TrimRemarks(basic.Intro, true, cutfrom);
|
||||
basic.Brief = GeneralUtils.TrimRemarks(basic.Brief, true, cutfrom);
|
||||
@ -2033,15 +2152,14 @@ namespace MobiusEditor.TiberianDawn
|
||||
}
|
||||
basic.Name = String.Join(" ", name);
|
||||
}
|
||||
INISection basicSection = ini.Sections.Add("Basic");
|
||||
INI.WriteSection(new MapContext(Map, false), basicSection, Map.BasicSection);
|
||||
INISection basicSection = INITools.FillAndReAdd(ini, "Basic", (BasicSection)Map.BasicSection, new MapContext(Map, false), true);
|
||||
return basicSection;
|
||||
}
|
||||
|
||||
protected INISection SaveIniMap(INI ini)
|
||||
{
|
||||
Map.MapSection.FixBounds();
|
||||
INISection mapSection = ini.Sections.Add("Map");
|
||||
INISection mapSection = INITools.FillAndReAdd(ini, "Map", Map.MapSection, new MapContext(Map, false), true);
|
||||
if (isMegaMap)
|
||||
{
|
||||
mapSection["Version"] = "1";
|
||||
@ -2347,13 +2465,17 @@ namespace MobiusEditor.TiberianDawn
|
||||
List<INISection> houseSections = new List<INISection>();
|
||||
foreach (var house in Map.Houses)
|
||||
{
|
||||
if ((house.Type.ID < 0) || !house.Enabled)
|
||||
if (house.Type.ID < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
INISection houseSection = ini.Sections.Add(house.Type.Name);
|
||||
INI.WriteSection(new MapContext(Map, false), houseSection, house);
|
||||
houseSections.Add(houseSection);
|
||||
House gameHouse = (House)house;
|
||||
bool enabled = house.Enabled;
|
||||
INISection houseSection = INITools.FillAndReAdd(ini, gameHouse.Type.Name, gameHouse, new MapContext(Map, false), enabled);
|
||||
if (houseSection != null)
|
||||
{
|
||||
houseSections.Add(houseSection);
|
||||
}
|
||||
}
|
||||
return houseSections;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
MISSION_AREA_GUARD,
|
||||
//MISSION_RETURN,
|
||||
MISSION_STOP,
|
||||
//MISSION_AMBUSH,
|
||||
MISSION_AMBUSH,
|
||||
MISSION_HUNT,
|
||||
//MISSION_TIMED_HUNT,
|
||||
MISSION_UNLOAD,
|
||||
|
@ -45,6 +45,8 @@ namespace MobiusEditor.Tools
|
||||
private readonly Dictionary<int, CellTrigger> undoCellTriggers = new Dictionary<int, CellTrigger>();
|
||||
private readonly Dictionary<int, CellTrigger> redoCellTriggers = new Dictionary<int, CellTrigger>();
|
||||
|
||||
private Map previewMap;
|
||||
protected override Map RenderMap => previewMap;
|
||||
private bool placementMode;
|
||||
|
||||
public string TriggerToolTip { get; set; }
|
||||
@ -52,6 +54,7 @@ namespace MobiusEditor.Tools
|
||||
public CellTriggersTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, ComboBox triggerCombo, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
|
||||
: base(mapPanel, layers, statusLbl, plugin, url)
|
||||
{
|
||||
previewMap = map;
|
||||
this.triggerComboBox = triggerCombo;
|
||||
UpdateDataSource();
|
||||
}
|
||||
@ -178,46 +181,48 @@ namespace MobiusEditor.Tools
|
||||
{
|
||||
RemoveCellTrigger(e.NewCell);
|
||||
}
|
||||
mapPanel.Invalidate(map, e.NewCell);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCellTrigger(Point location)
|
||||
{
|
||||
if (triggerComboBox.SelectedItem is string trigger && !Trigger.IsEmpty(trigger))
|
||||
if (!(triggerComboBox.SelectedItem is string trigger) || Trigger.IsEmpty(trigger))
|
||||
{
|
||||
if (map.Metrics.GetCell(location, out int cell))
|
||||
{
|
||||
if (map.CellTriggers[cell] == null)
|
||||
{
|
||||
if (!undoCellTriggers.ContainsKey(cell))
|
||||
{
|
||||
undoCellTriggers[cell] = map.CellTriggers[cell];
|
||||
}
|
||||
var cellTrigger = new CellTrigger(trigger);
|
||||
map.CellTriggers[cell] = cellTrigger;
|
||||
redoCellTriggers[cell] = cellTrigger;
|
||||
mapPanel.Invalidate();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!map.Metrics.GetCell(location, out int cell) || map.CellTriggers[cell] != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!undoCellTriggers.ContainsKey(cell))
|
||||
{
|
||||
undoCellTriggers[cell] = map.CellTriggers[cell];
|
||||
}
|
||||
var cellTrigger = new CellTrigger(trigger);
|
||||
map.CellTriggers[cell] = cellTrigger;
|
||||
redoCellTriggers[cell] = cellTrigger;
|
||||
mapPanel.Invalidate(map, navigationWidget.MouseCell);
|
||||
}
|
||||
|
||||
private void RemoveCellTrigger(Point location)
|
||||
{
|
||||
if (map.Metrics.GetCell(location, out int cell))
|
||||
if (!map.Metrics.GetCell(location, out int cell))
|
||||
{
|
||||
var cellTrigger = map.CellTriggers[cell];
|
||||
if (cellTrigger != null)
|
||||
{
|
||||
if (!undoCellTriggers.ContainsKey(cell))
|
||||
{
|
||||
undoCellTriggers[cell] = map.CellTriggers[cell];
|
||||
}
|
||||
map.CellTriggers[cell] = null;
|
||||
redoCellTriggers[cell] = null;
|
||||
mapPanel.Invalidate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
var cellTrigger = map.CellTriggers[cell];
|
||||
if (cellTrigger == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!undoCellTriggers.ContainsKey(cell))
|
||||
{
|
||||
undoCellTriggers[cell] = map.CellTriggers[cell];
|
||||
}
|
||||
map.CellTriggers[cell] = null;
|
||||
redoCellTriggers[cell] = null;
|
||||
mapPanel.Invalidate(map, navigationWidget.MouseCell);
|
||||
}
|
||||
|
||||
private void EnterPlacementMode()
|
||||
@ -227,6 +232,7 @@ namespace MobiusEditor.Tools
|
||||
return;
|
||||
}
|
||||
placementMode = true;
|
||||
mapPanel.Invalidate(map, navigationWidget.MouseCell);
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
@ -237,6 +243,7 @@ namespace MobiusEditor.Tools
|
||||
return;
|
||||
}
|
||||
placementMode = false;
|
||||
mapPanel.Invalidate(map, navigationWidget.MouseCell);
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
@ -265,8 +272,8 @@ namespace MobiusEditor.Tools
|
||||
CellTrigger cellTrig = kv.Value;
|
||||
bool isValid = cellTrig == null || valid.Any(t => t.Name.Equals(cellTrig.Trigger, StringComparison.InvariantCultureIgnoreCase));
|
||||
e.Map.CellTriggers[kv.Key] = isValid ? cellTrig : null;
|
||||
e.MapPanel.Invalidate(map, kv.Key);
|
||||
}
|
||||
e.MapPanel.Invalidate();
|
||||
if (e.Plugin != null)
|
||||
{
|
||||
e.Plugin.Dirty = origDirtyState;
|
||||
@ -281,8 +288,8 @@ namespace MobiusEditor.Tools
|
||||
CellTrigger cellTrig = kv.Value;
|
||||
bool isValid = cellTrig == null || valid.Any(t => t.Name.Equals(cellTrig.Trigger, StringComparison.InvariantCultureIgnoreCase));
|
||||
e.Map.CellTriggers[kv.Key] = isValid ? cellTrig : null;
|
||||
e.MapPanel.Invalidate(map, kv.Key);
|
||||
}
|
||||
e.MapPanel.Invalidate();
|
||||
if (e.Plugin != null)
|
||||
{
|
||||
e.Plugin.Dirty = true;
|
||||
@ -295,20 +302,50 @@ namespace MobiusEditor.Tools
|
||||
|
||||
private void TriggerCombo_SelectedIndexChanged(System.Object sender, System.EventArgs e)
|
||||
{
|
||||
mapPanel.Invalidate();
|
||||
mapPanel.Invalidate(map, navigationWidget.MouseCell);
|
||||
}
|
||||
|
||||
protected override void PreRenderMap()
|
||||
{
|
||||
base.PreRenderMap();
|
||||
previewMap = map.Clone();
|
||||
if (!placementMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string selected = triggerComboBox.SelectedItem as string;
|
||||
if (selected == null || Trigger.IsEmpty(selected))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var location = navigationWidget.MouseCell;
|
||||
if (!previewMap.Metrics.GetCell(location, out int cell))
|
||||
{
|
||||
return;
|
||||
}
|
||||
CellTrigger celltr = previewMap.CellTriggers[location];
|
||||
if (celltr == null)
|
||||
{
|
||||
previewMap.CellTriggers[location] = new CellTrigger(selected);
|
||||
// Tint is not actually used; a lower alpha just indicates that it is a preview item.
|
||||
previewMap.CellTriggers[location].Tint = Color.FromArgb(128, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PostRenderMap(Graphics graphics)
|
||||
{
|
||||
base.PostRenderMap(graphics);
|
||||
string selected = triggerComboBox.SelectedItem as string;
|
||||
if (selected != null && Trigger.IsEmpty(selected))
|
||||
selected = null;
|
||||
string[] selectedRange = selected != null ? new[] { selected } : new string[] { };
|
||||
// Normal techno triggers: under cell
|
||||
MapRenderer.RenderAllTechnoTriggers(graphics, map, Globals.MapTileSize, Globals.MapTileScale, Layers, Color.LimeGreen, selected, true);
|
||||
MapRenderer.RenderCellTriggers(graphics, map, Globals.MapTileSize, Globals.MapTileScale, selectedRange);
|
||||
if (selected != null)
|
||||
{
|
||||
MapRenderer.RenderCellTriggers(graphics, map, Globals.MapTileSize, Globals.MapTileScale, Color.Black, Color.Yellow, Color.Yellow, true, false, selectedRange);
|
||||
// Only use preview map if in placement mode.
|
||||
MapRenderer.RenderCellTriggers(graphics, placementMode ? previewMap : map, Globals.MapTileSize, Globals.MapTileScale, Color.Black, Color.Yellow, Color.Yellow, true, false, selectedRange);
|
||||
// Selected technos: on top of cell
|
||||
MapRenderer.RenderAllTechnoTriggers(graphics, map, Globals.MapTileSize, Globals.MapTileScale, Layers, Color.Yellow, selected, false);
|
||||
}
|
||||
|
@ -488,7 +488,7 @@ namespace MobiusEditor.Tools
|
||||
{
|
||||
string owningType = toFind.GroupTiles[0];
|
||||
TemplateType group = map.TemplateTypes.Where(t => t.Name.Equals(owningType, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
|
||||
templatesToFind.UnionWith(map.TemplateTypes.Where(t => group.GroupTiles.Contains(t.Name, StringComparer.InvariantCultureIgnoreCase)));
|
||||
templatesToFind.UnionWith(map.TemplateTypes.Where(t => group.GroupTiles.Contains(t.Name, StringComparer.OrdinalIgnoreCase)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -517,7 +517,7 @@ namespace MobiusEditor.Tools
|
||||
{
|
||||
string owningType = tp.GroupTiles[0];
|
||||
TemplateType group = map.TemplateTypes.Where(t => t.Name.Equals(owningType, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
|
||||
templatesToFind.UnionWith(map.TemplateTypes.Where(t => group.GroupTiles.Contains(t.Name, StringComparer.InvariantCultureIgnoreCase)));
|
||||
templatesToFind.UnionWith(map.TemplateTypes.Where(t => group.GroupTiles.Contains(t.Name, StringComparer.OrdinalIgnoreCase)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -78,7 +78,6 @@ namespace MobiusEditor.Tools
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e)
|
||||
{
|
||||
if (placementMode)
|
||||
|
@ -173,21 +173,6 @@ namespace MobiusEditor.Utility
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckForIniInfo(INI iniContents, string section)
|
||||
{
|
||||
return CheckForIniInfo(iniContents, section, null, null);
|
||||
}
|
||||
|
||||
public static bool CheckForIniInfo(INI iniContents, string section, string key, string value)
|
||||
{
|
||||
INISection iniSection = iniContents[section];
|
||||
if (key == null || value == null)
|
||||
{
|
||||
return iniSection != null;
|
||||
}
|
||||
return iniSection != null && iniSection.Keys.Contains(key) && iniSection[key].Trim() == value;
|
||||
}
|
||||
|
||||
public static String MakeNew4CharName(IEnumerable<string> currentList, string fallback, params string[] reservedNames)
|
||||
{
|
||||
string name = string.Empty;
|
||||
@ -201,7 +186,7 @@ namespace MobiusEditor.Utility
|
||||
for (int l = 'a'; l <= 'z'; ++l)
|
||||
{
|
||||
name = String.Concat((char)i, (char)j, (char)k, (char)l);
|
||||
if (!currentList.Contains(name, StringComparer.InvariantCultureIgnoreCase) && !reservedNames.Contains(name, StringComparer.InvariantCultureIgnoreCase))
|
||||
if (!currentList.Contains(name, StringComparer.InvariantCultureIgnoreCase) && !reservedNames.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
@ -44,17 +44,6 @@ namespace MobiusEditor.Utility
|
||||
}
|
||||
return string.Empty;
|
||||
};
|
||||
|
||||
public static bool IsValidKey(String iniKey, params string[] reservedNames)
|
||||
{
|
||||
foreach (string name in reservedNames) {
|
||||
if (name.Equals(iniKey, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return iniKey.All(c => c > ' ' && c <= '~' && c != '=' && c != '[' && c != ']');
|
||||
}
|
||||
}
|
||||
|
||||
public class INIKeyValueCollection : IEnumerable<(string Key, string Value)>, IEnumerable
|
||||
@ -594,6 +583,20 @@ 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)
|
||||
{
|
||||
if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
section.Keys.Remove(property.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteSection<T>(ITypeDescriptorContext context, INISection section, T data)
|
||||
{
|
||||
var propertyDescriptors = TypeDescriptor.GetProperties(data);
|
||||
|
143
CnCTDRAMapEditor/Utility/INITools.cs
Normal file
143
CnCTDRAMapEditor/Utility/INITools.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using MobiusEditor.Model;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
{
|
||||
public static class INITools
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether certain ini information was found in the given ini data.
|
||||
/// </summary>
|
||||
/// <param name="ini">ini data.</param>
|
||||
/// <param name="section">Section to find.</param>
|
||||
/// <returns>True if the ini section was found.</returns>
|
||||
public static bool CheckForIniInfo(INI ini, string section)
|
||||
{
|
||||
return CheckForIniInfo(ini, section, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether certain ini information was found in the given ini data.
|
||||
/// </summary>
|
||||
/// <param name="ini">ini data.</param>
|
||||
/// <param name="section">Section to find.</param>
|
||||
/// <param name="key">Optional key to find. If no complete key/value pair is given, only the existence of the section will be checked.</param>
|
||||
/// <param name="value">Optional value to find. If no complete key/value pair is given, only the existence of the section will be checked.</param>
|
||||
/// <returns>True if the ini information was found.</returns>
|
||||
public static bool CheckForIniInfo(INI ini, string section, string key, string value)
|
||||
{
|
||||
INISection iniSection = ini[section];
|
||||
if (key == null || value == null)
|
||||
{
|
||||
return iniSection != null;
|
||||
}
|
||||
return iniSection != null && iniSection.Keys.Contains(key) && iniSection[key].Trim() == value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given string is a valid ini key in an ASCII context.
|
||||
/// </summary>
|
||||
/// <param name="iniKey">The key to check.</param>
|
||||
/// <param name="reservedNames">Optional array of reserved names. IF given, any entry in this list will also return false.</param>
|
||||
/// <returns>True if the given string is a valid ini key in an ASCII context.</returns>
|
||||
public static bool IsValidKey(String iniKey, params string[] reservedNames)
|
||||
{
|
||||
if (reservedNames != null)
|
||||
{
|
||||
foreach (string name in reservedNames)
|
||||
{
|
||||
if (name.Equals(iniKey, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return iniKey.All(c => c > ' ' && c <= '~' && c != '=' && c != '[' && c != ']');
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var dataSection = ini.Sections[name];
|
||||
if (dataSection == null)
|
||||
return null;
|
||||
INI.ParseSection(context, dataSection, data);
|
||||
INI.RemoveHandledKeys(dataSection, data);
|
||||
if (dataSection.Keys.Count() == 0)
|
||||
ini.Sections.Remove(name);
|
||||
return dataSection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will extract a section from the ini information, add the current data to it, and re-add it
|
||||
/// at the end of the ini object. If the <see cref="shouldAdd" /> argument is false, and no section
|
||||
/// with this name is found in the current ini object, the object is not added. Otherwise it
|
||||
/// will be added, with the data object info added into it.
|
||||
/// </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 write data.</param>
|
||||
/// <param name="shouldAdd">False if the object is not supposed to be added. This will be ignored if a section with that name is found that contains keys not managed by the data object.</param>
|
||||
/// <returns>Null if the section was not found, otherwise the final re-added section.</returns>
|
||||
public static INISection FillAndReAdd<T>(INI ini, string name, T data, MapContext context, bool shouldAdd)
|
||||
{
|
||||
INISection dataSection = ini.Sections.Extract(name);
|
||||
if (dataSection != null)
|
||||
{
|
||||
INI.RemoveHandledKeys(dataSection, data);
|
||||
if (dataSection.Keys.Count > 0)
|
||||
{
|
||||
// Contains extra keys.
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
if (!shouldAdd)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (dataSection != null)
|
||||
{
|
||||
ini.Sections.Add(dataSection);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataSection = ini.Sections.Add(name);
|
||||
}
|
||||
INI.WriteSection(context, dataSection, data);
|
||||
return dataSection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will seek a section in the ini, remove any information in it that is handled by the data object,
|
||||
/// and remove the section from the ini if no keys remain in it.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data.</typeparam>
|
||||
/// <param name="ini">Ini object.</param>
|
||||
/// <param name="data">Data object.</param>
|
||||
/// <param name="name">Name of the section.</param>
|
||||
public static void ClearDataFrom<T>(INI ini, string name, T data)
|
||||
{
|
||||
var basicSection = ini.Sections[name];
|
||||
if (basicSection != null)
|
||||
{
|
||||
INI.RemoveHandledKeys(basicSection, data);
|
||||
if (basicSection.Keys.Count() == 0)
|
||||
ini.Sections.Remove(name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user