diff --git a/DXMainClient/DXGUI/Generic/Campaign/VariableCheckbox.cs b/DXMainClient/DXGUI/Generic/Campaign/VariableCheckbox.cs new file mode 100644 index 000000000..963085158 --- /dev/null +++ b/DXMainClient/DXGUI/Generic/Campaign/VariableCheckbox.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using DTAClient.Domain.Singleplayer; + +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Generic.Campaign +{ + + public class VariableCheckbox : XNACheckBox + { + private string _variable; + public string Variable + { + get + { + return _variable; + } + set + { + Checked = CampaignHandler.Instance.Variables[value] > 0; + _variable = value; + } + } + public VariableCheckbox(WindowManager windowManager) : base(windowManager) + { + AllowChecking = true; + } + public override void OnLeftClick() + { + base.OnLeftClick(); + + if (CampaignHandler.Instance.Variables.ContainsKey(Variable)) + CampaignHandler.Instance.Variables[Variable] = Checked ? 1 : 0; + } + } +} diff --git a/DXMainClient/DXGUI/Generic/CampaignSelector.cs b/DXMainClient/DXGUI/Generic/CampaignSelector.cs index d7aebdaa0..ec4114e75 100644 --- a/DXMainClient/DXGUI/Generic/CampaignSelector.cs +++ b/DXMainClient/DXGUI/Generic/CampaignSelector.cs @@ -11,6 +11,7 @@ using ClientUpdater; using ClientCore.Extensions; using DTAClient.Domain.Singleplayer; +using DTAClient.DXGUI.Generic.Campaign; namespace DTAClient.DXGUI.Generic { @@ -20,14 +21,6 @@ public class CampaignSelector : XNAWindow private const int DEFAULT_HEIGHT = 600; private static string[] DifficultyNames = new string[] { "Easy", "Medium", "Hard" }; - - private static string[] DifficultyIniPaths = new string[] - { - "INI/Map Code/Difficulty Easy.ini", - "INI/Map Code/Difficulty Medium.ini", - "INI/Map Code/Difficulty Hard.ini" - }; - public CampaignSelector(WindowManager windowManager, DiscordHandler discordHandler) : base(windowManager) { this.discordHandler = discordHandler; @@ -35,12 +28,18 @@ public CampaignSelector(WindowManager windowManager, DiscordHandler discordHandl private DiscordHandler discordHandler; - private List Missions = new List(); private XNAListBox lbCampaignList; private XNAClientButton btnLaunch; private XNATextBlock tbMissionDescription; private XNATrackbar trbDifficultySelector; + private const int VAR_MAX = 10; + + private XNALabel lblVariablesHeader; + private XNALabel[] variableNames = new XNALabel[VAR_MAX]; + private ToolTip[] variableToolTips = new ToolTip[VAR_MAX]; + private VariableCheckbox[] variableValues = new VariableCheckbox[VAR_MAX]; + private CheaterWindow cheaterWindow; private string[] filesToCheck = new string[] @@ -159,6 +158,40 @@ public override void Initialize() btnCancel.Text = "Cancel".L10N("Client:Main:ButtonCancel"); btnCancel.LeftClick += BtnCancel_LeftClick; + int y = (lblDifficultyLevel.Y - (UIDesignConstants.CONTROL_VERTICAL_MARGIN * 2) - UIDesignConstants.BUTTON_HEIGHT + 1) - UIDesignConstants.EMPTY_SPACE_BOTTOM; + + for (int i = 0; i < VAR_MAX; i++) + { + variableValues[i] = new VariableCheckbox(WindowManager); + variableValues[i].Name = "variableValue" + i; + AddChild(variableValues[i]); + variableValues[i].X = trbDifficultySelector.ClientRectangle.Center.X + tbMissionDescription.Width / 4; + variableValues[i].Y = y - (UIDesignConstants.EMPTY_SPACE_BOTTOM * 2) - variableValues[i].Height; + variableValues[i].Disable(); + + variableNames[i] = new XNALabel(WindowManager); + variableNames[i].Name = "variableName" + i; + variableNames[i].Text = "Variable #" + i; + variableNames[i].TextAnchor = LabelTextAnchorInfo.RIGHT; + variableNames[i].AnchorPoint = new Vector2(trbDifficultySelector.ClientRectangle.Center.X - tbMissionDescription.Width / 4, variableValues[i].Y - 1); + AddChild(variableNames[i]); + variableNames[i].Disable(); + + variableToolTips[i] = new ToolTip(WindowManager, variableNames[i]); + y = variableNames[i].Y; + + } + + lblVariablesHeader = new XNALabel(WindowManager); + lblVariablesHeader.Name = nameof(lblVariablesHeader); + lblVariablesHeader.FontIndex = 1; + lblVariablesHeader.TextAnchor = LabelTextAnchorInfo.HORIZONTAL_CENTER; + lblVariablesHeader.AnchorPoint = new Vector2(trbDifficultySelector.ClientRectangle.Center.X, + variableNames[0].Y - UIDesignConstants.CONTROL_VERTICAL_MARGIN * 2); + lblVariablesHeader.Text = "GLOBAL VARIABLES"; + AddChild(lblVariablesHeader); + lblVariablesHeader.Disable(); + AddChild(lblSelectCampaign); AddChild(lblMissionDescriptionHeader); AddChild(lbCampaignList); @@ -179,7 +212,7 @@ public override void Initialize() trbDifficultySelector.Value = UserINISettings.Instance.Difficulty; - ReadMissionList(); + ListMissions(); cheaterWindow = new CheaterWindow(WindowManager); var dp = new DarkeningPanel(WindowManager); @@ -200,7 +233,7 @@ private void LbCampaignList_SelectedIndexChanged(object sender, EventArgs e) return; } - Mission mission = Missions[lbCampaignList.SelectedIndex]; + Mission mission = CampaignHandler.Instance.Missions[lbCampaignList.SelectedIndex]; if (string.IsNullOrEmpty(mission.Scenario)) { @@ -217,9 +250,55 @@ private void LbCampaignList_SelectedIndexChanged(object sender, EventArgs e) return; } + ConfigureVariableUI(mission); btnLaunch.AllowClick = true; } + private void ConfigureVariableUI(Mission mission) + { + lblVariablesHeader.Disable(); + for(int i = 0; i < VAR_MAX; i++) + { + variableNames[i].Disable(); + variableValues[i].Disable(); + } + + tbMissionDescription.Height = variableValues[0].Bottom - tbMissionDescription.Y; + + if (mission != null && mission.ConfigurableVariables.Count > 0) + { + lblVariablesHeader.Enable(); + + for (int i = 0; i < mission.ConfigurableVariables.Count && i < VAR_MAX; i++) + { + string[] components = mission.ConfigurableVariables[i].Split(','); + + if (components.Length != 3) + { + Logger.Log("Syntax Error For Configurable Mission Variable: " + mission.ConfigurableVariables[i]); + return; + } + + variableNames[i].Text = components[1]; + variableNames[i].TextColor = UISettings.ActiveSettings.TextColor; + variableNames[i].Enable(); + variableToolTips[i].Text = components[2]; + + variableValues[i].Variable = components[0]; + variableValues[i].Enable(); + } + + int y = mission.ConfigurableVariables.Count > VAR_MAX ? variableNames[0].Y : + variableNames[mission.ConfigurableVariables.Count - 1].Y; + y -= UIDesignConstants.CONTROL_VERTICAL_MARGIN * 4; + lblVariablesHeader.Y = y; + lblVariablesHeader.Enable(); + + tbMissionDescription.Height = lblVariablesHeader.Y - (UIDesignConstants.CONTROL_VERTICAL_MARGIN * 2) - tbMissionDescription.Y; + + } + } + private void BtnCancel_LeftClick(object sender, EventArgs e) { Enabled = false; @@ -229,7 +308,7 @@ private void BtnLaunch_LeftClick(object sender, EventArgs e) { int selectedMissionId = lbCampaignList.SelectedIndex; - Mission mission = Missions[selectedMissionId]; + Mission mission = CampaignHandler.Instance.Missions[selectedMissionId]; if (!ClientConfiguration.Instance.ModMode && (!Updater.IsFileNonexistantOrOriginal(mission.Scenario) || AreFilesModified())) @@ -243,6 +322,33 @@ private void BtnLaunch_LeftClick(object sender, EventArgs e) LaunchMission(mission); } + private void ListMissions() + { + foreach (var mission in CampaignHandler.Instance.Missions) + { + var item = new XNAListBoxItem(); + item.Text = mission.GUIName; + if (!mission.Enabled) + { + item.TextColor = UISettings.ActiveSettings.DisabledItemColor; + } + else if (string.IsNullOrEmpty(mission.Scenario)) + { + item.TextColor = AssetLoader.GetColorFromString(ClientConfiguration.Instance.ListBoxHeaderColor); + item.IsHeader = true; + item.Selectable = false; + } + else + { + item.TextColor = lbCampaignList.DefaultItemColor; + } + if (!string.IsNullOrEmpty(mission.IconPath)) + item.Texture = AssetLoader.LoadTexture(mission.IconPath + "icon.png"); + + lbCampaignList.AddItem(item); + } + } + private bool AreFilesModified() { foreach (string filePath in filesToCheck) @@ -268,60 +374,11 @@ private void CheaterWindow_YesClicked(object sender, EventArgs e) /// private void LaunchMission(Mission mission) { - bool copyMapsToSpawnmapINI = ClientConfiguration.Instance.CopyMissionsToSpawnmapINI; - - Logger.Log("About to write spawn.ini."); - using (var spawnStreamWriter = new StreamWriter(SafePath.CombineFilePath(ProgramConstants.GamePath, "spawn.ini"))) - { - spawnStreamWriter.WriteLine("; Generated by DTA Client"); - spawnStreamWriter.WriteLine("[Settings]"); - if (copyMapsToSpawnmapINI) - spawnStreamWriter.WriteLine("Scenario=spawnmap.ini"); - else - spawnStreamWriter.WriteLine("Scenario=" + mission.Scenario); - - // No one wants to play missions on Fastest, so we'll change it to Faster - if (UserINISettings.Instance.GameSpeed == 0) - UserINISettings.Instance.GameSpeed.Value = 1; - - spawnStreamWriter.WriteLine("CampaignID=" + mission.CampaignID); - spawnStreamWriter.WriteLine("GameSpeed=" + UserINISettings.Instance.GameSpeed); -#if YR || ARES - spawnStreamWriter.WriteLine("Ra2Mode=" + !mission.RequiredAddon); -#else - spawnStreamWriter.WriteLine("Firestorm=" + mission.RequiredAddon); -#endif - spawnStreamWriter.WriteLine("CustomLoadScreen=" + LoadingScreenController.GetLoadScreenName(mission.Side.ToString())); - spawnStreamWriter.WriteLine("IsSinglePlayer=Yes"); - spawnStreamWriter.WriteLine("SidebarHack=" + ClientConfiguration.Instance.SidebarHack); - spawnStreamWriter.WriteLine("Side=" + mission.Side); - spawnStreamWriter.WriteLine("BuildOffAlly=" + mission.BuildOffAlly); - - UserINISettings.Instance.Difficulty.Value = trbDifficultySelector.Value; - - spawnStreamWriter.WriteLine("DifficultyModeHuman=" + (mission.PlayerAlwaysOnNormalDifficulty ? "1" : trbDifficultySelector.Value.ToString())); - spawnStreamWriter.WriteLine("DifficultyModeComputer=" + GetComputerDifficulty()); - - spawnStreamWriter.WriteLine(); - spawnStreamWriter.WriteLine(); - spawnStreamWriter.WriteLine(); - } - - var difficultyIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, DifficultyIniPaths[trbDifficultySelector.Value])); - string difficultyName = DifficultyNames[trbDifficultySelector.Value]; - - if (copyMapsToSpawnmapINI) - { - var mapIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, mission.Scenario)); - IniFile.ConsolidateIniFiles(mapIni, difficultyIni); - mapIni.WriteIniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, "spawnmap.ini")); - } - - UserINISettings.Instance.Difficulty.Value = trbDifficultySelector.Value; - UserINISettings.Instance.SaveSettings(); + CampaignHandler.Instance.WriteFilesForMission(mission, trbDifficultySelector.Value); ((MainMenuDarkeningPanel)Parent).Hide(); + string difficultyName = DifficultyNames[trbDifficultySelector.Value]; discordHandler.UpdatePresence(mission.UntranslatedGUIName, difficultyName, mission.IconPath, true); GameProcessLogic.GameProcessExited += GameProcessExited_Callback; @@ -341,82 +398,7 @@ protected virtual void GameProcessExited() GameProcessLogic.GameProcessExited -= GameProcessExited_Callback; // Logger.Log("GameProcessExited: Updating Discord Presence."); discordHandler.UpdatePresence(); - } - - private void ReadMissionList() - { - ParseBattleIni("INI/Battle.ini"); - - if (Missions.Count == 0) - ParseBattleIni("INI/" + ClientConfiguration.Instance.BattleFSFileName); - } - - /// - /// Parses a Battle(E).ini file. Returns true if succesful (file found), otherwise false. - /// - /// The path of the file, relative to the game directory. - /// True if succesful, otherwise false. - private bool ParseBattleIni(string path) - { - Logger.Log("Attempting to parse " + path + " to populate mission list."); - - FileInfo battleIniFileInfo = SafePath.GetFile(ProgramConstants.GamePath, path); - if (!battleIniFileInfo.Exists) - { - Logger.Log("File " + path + " not found. Ignoring."); - return false; - } - - if (Missions.Count > 0) - { - throw new InvalidOperationException("Loading multiple Battle*.ini files is not supported anymore."); - } - - var battleIni = new IniFile(battleIniFileInfo.FullName); - - List battleKeys = battleIni.GetSectionKeys("Battles"); - - if (battleKeys == null) - return false; // File exists but [Battles] doesn't - - for (int i = 0; i < battleKeys.Count; i++) - { - string battleEntry = battleKeys[i]; - string battleSection = battleIni.GetStringValue("Battles", battleEntry, "NOT FOUND"); - - if (!battleIni.SectionExists(battleSection)) - continue; - - var mission = new Mission(battleIni.GetSection(battleSection)); - - Missions.Add(mission); - - var item = new XNAListBoxItem(); - item.Text = mission.GUIName; - if (!mission.Enabled) - { - item.TextColor = UISettings.ActiveSettings.DisabledItemColor; - } - else if (string.IsNullOrEmpty(mission.Scenario)) - { - item.TextColor = AssetLoader.GetColorFromString( - ClientConfiguration.Instance.ListBoxHeaderColor); - item.IsHeader = true; - item.Selectable = false; - } - else - { - item.TextColor = lbCampaignList.DefaultItemColor; - } - - if (!string.IsNullOrEmpty(mission.IconPath)) - item.Texture = AssetLoader.LoadTexture(mission.IconPath + "icon.png"); - - lbCampaignList.AddItem(item); - } - - Logger.Log("Finished parsing " + path + "."); - return true; + CampaignHandler.Instance.CampaignPostGame(missionToLaunch); } public override void Draw(GameTime gameTime) diff --git a/DXMainClient/Domain/Singleplayer/CampaignBindSet.cs b/DXMainClient/Domain/Singleplayer/CampaignBindSet.cs new file mode 100644 index 000000000..b5c4da1c2 --- /dev/null +++ b/DXMainClient/Domain/Singleplayer/CampaignBindSet.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Text.RegularExpressions; + +using Rampastring.Tools; +using ClientCore; + +namespace DTAClient.Domain.Singleplayer +{ + public class CampaignBindSet + { + public string Prefix; + public Dictionary Bindings; + + public CampaignBindSet(string prefix) + { + Prefix = prefix; + Bindings = new Dictionary(); + } + + public void InitFromIniSection(IniSection section) + { + foreach (KeyValuePair kvp in section.Keys.Where(k => k.Key.StartsWith(Prefix))) + { + string[] parts = kvp.Key.Split('.'); + if (parts.Length > 2) + Logger.Log("Campaign binding key containing more than one /'./' will be skipped: " + kvp.Key); + else + Bindings.Add(parts[1],kvp.Value); + } + } + } +} diff --git a/DXMainClient/Domain/Singleplayer/CampaignBinding.cs b/DXMainClient/Domain/Singleplayer/CampaignBinding.cs deleted file mode 100644 index 2813eda8b..000000000 --- a/DXMainClient/Domain/Singleplayer/CampaignBinding.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Rampastring.Tools; - -using System; -using System.Collections.Generic; - - -namespace DTAClient.Domain.Singleplayer -{ - /// - /// Represents a relationship between a client variable and related in-game events or configurations - /// - public class CampaignBinding - { - public CampaignBinding(string key) - { - Key = key; - } - public string Key { get; set; } - public Dictionary Binds { get; } = new Dictionary(); - - public void Bind(string str) - { - // Syntax: - // Variable1:State1,Variable2:State2,Variable3:State3,... - - string[] binds = str.Split(','); - foreach (var binding in binds) - { - string[] components = binding.Split(':'); - if (components.Length != 2) - { - Logger.Log("Parsing CampaignBinding from \"" + str + "\" failed:" + binding); - continue; - } - - Binds.Add(components[0], components[1]); - } - } - } -} diff --git a/DXMainClient/Domain/Singleplayer/CampaignHandler.cs b/DXMainClient/Domain/Singleplayer/CampaignHandler.cs index bdaf7b869..777fd3e3b 100644 --- a/DXMainClient/Domain/Singleplayer/CampaignHandler.cs +++ b/DXMainClient/Domain/Singleplayer/CampaignHandler.cs @@ -1,13 +1,12 @@ using ClientCore; - -using Microsoft.Extensions.Options; +using ClientCore.Statistics; using Rampastring.Tools; using System; using System.Collections.Generic; using System.IO; -using System.Reflection; -using System.Reflection.Metadata; +using System.Linq; +using System.Text.RegularExpressions; namespace DTAClient.Domain.Singleplayer { @@ -21,22 +20,30 @@ public CampaignConfigException(string message) : base(message) { } /// public class CampaignHandler { - private const string CAMPAIGN_INI = "INI/Campaign.ini"; - private const string VARS_SECTION = "CampaignVariables"; private CampaignHandler() { ReadBattleIni("INI/Battle.ini"); ReadBattleIni("INI/" + ClientConfiguration.Instance.BattleFSFileName); - //CareerHandler.ReadCareerData(Missions, Variables); + CareerHandler.ReadCareerData(Missions, Variables); ValidateConfiguration(); } - /// - /// Singleton pattern. Only one instance of this class can exist. - /// + public List Missions = new List(); + public Dictionary Variables = new Dictionary(); + + private static Regex GameVariableFormat = new Regex(@"^[lg]\d+"); + + private static string[] DifficultyIniPaths = new string[] + { + "INI/Map Code/Difficulty Easy.ini", + "INI/Map Code/Difficulty Medium.ini", + "INI/Map Code/Difficulty Hard.ini" + }; + + private static CampaignHandler _instance; public static CampaignHandler Instance { @@ -48,28 +55,27 @@ public static CampaignHandler Instance } } - public List Missions = new List(); - public Dictionary Variables = new Dictionary(); - /// /// Reads all the missions defined in the specified battle ini file. /// Missions must have only one section, no overriding or piecemeal entries. /// - private void ReadBattleIni(string path) + private bool ReadBattleIni(string path) { - string iniPath = ProgramConstants.GamePath + path; + Logger.Log("Attempting to parse " + path + " to populate mission list."); - if (!File.Exists(iniPath)) + FileInfo iniFileInfo = SafePath.GetFile(ProgramConstants.GamePath, path); + + if (!iniFileInfo.Exists) { Logger.Log("File " + path + " not found. Ignoring."); - return; + return false; } - - var battleIni = new IniFile(iniPath); + + var battleIni = new IniFile(iniFileInfo.FullName); List battleKeys = battleIni.GetSectionKeys("Battles"); if (battleKeys == null) - return; // File exists but [Battles] doesn't + return false; // File exists but [Battles] doesn't foreach (string battleEntry in battleKeys) { @@ -78,14 +84,15 @@ private void ReadBattleIni(string path) if (!battleIni.SectionExists(battleSection)) continue; - - if (Missions.Exists(m => m.InternalName == battleSection)) - throw new CampaignConfigException($"Multiple entries found for mission name: " + battleSection); + // Mission mission = Missions.Find(m => m.InternalName == battleSection); + // TODO Update duplicate Mission mission = new Mission(battleIni.GetSection(battleSection)); Missions.Add(mission); - } + } + Logger.Log("Finished parsing " + path + "."); + return true; } /// @@ -94,7 +101,7 @@ private void ReadBattleIni(string path) /// private void ValidateConfiguration() { - foreach (var mission in Missions) + foreach (var mission in Missions.ToList()) { string root = ProgramConstants.GamePath; @@ -107,16 +114,193 @@ private void ValidateConfiguration() continue; } - foreach (var binding in mission.MissionUnlocks) + // Make sure every variable mentioned is defined in the variables dictionary + // Could probably get mod makers to define a list instead of this mess? + foreach(var kvp in mission.LocalBindings) + { + if (!Variables.ContainsKey(kvp.Key)) + Variables.Add(kvp.Key, 0); + } + foreach (var kvp in mission.GlobalBindings) + { + if (!Variables.ContainsKey(kvp.Key)) + Variables.Add(kvp.Key, 0); + } + foreach (var kvp in mission.LocalUpdates) + { + if (!Variables.ContainsKey(kvp.Key)) + Variables.Add(kvp.Key, 0); + } + foreach (var kvp in mission.GlobalUpdates) + { + if (!Variables.ContainsKey(kvp.Key)) + Variables.Add(kvp.Key, 0); + } + } + } + + public void CampaignPostGame(Mission mission) + { + Dictionary gameVars = new Dictionary(); + + void VariablesFromIni(string ini) + { + FileInfo iniInfo = SafePath.GetFile(ProgramConstants.GamePath, ini); + if (!iniInfo.Exists) + return; + + IniSection section = new IniFile(iniInfo.FullName).GetSection("spawnmap.ini"); + if (section.Keys.Count == 0) + return; + + for (int i = 0; i < section.Keys.Count; i++) + { + gameVars.Add(ini[0] + i.ToString(), int.Parse(section.Keys[i].Value)); + } + } + + VariablesFromIni("globals.ini"); + VariablesFromIni("locals.ini"); + + // TODO Updates + } + + public void WriteFilesForMission(Mission mission, int difficulty) + { + bool copyMapsToSpawnmapINI = ClientConfiguration.Instance.CopyMissionsToSpawnmapINI; + + Logger.Log("About to write spawn.ini."); + using (var spawnStreamWriter = new StreamWriter(SafePath.CombineFilePath(ProgramConstants.GamePath, "spawn.ini"))) + { + spawnStreamWriter.WriteLine("; Generated by DTA Client"); + spawnStreamWriter.WriteLine("[Settings]"); + if (copyMapsToSpawnmapINI) + spawnStreamWriter.WriteLine("Scenario=spawnmap.ini"); + else + spawnStreamWriter.WriteLine("Scenario=" + mission.Scenario); + + // No one wants to play missions on Fastest, so we'll change it to Faster + if (UserINISettings.Instance.GameSpeed == 0) + UserINISettings.Instance.GameSpeed.Value = 1; + + spawnStreamWriter.WriteLine("CampaignID=" + mission.CampaignID); + spawnStreamWriter.WriteLine("GameSpeed=" + UserINISettings.Instance.GameSpeed); +#if YR || ARES + spawnStreamWriter.WriteLine("Ra2Mode=" + !mission.RequiredAddon); +#else + spawnStreamWriter.WriteLine("Firestorm=" + mission.RequiredAddon); +#endif + spawnStreamWriter.WriteLine("CustomLoadScreen=" + LoadingScreenController.GetLoadScreenName(mission.Side.ToString())); + spawnStreamWriter.WriteLine("IsSinglePlayer=Yes"); + spawnStreamWriter.WriteLine("SidebarHack=" + ClientConfiguration.Instance.SidebarHack); + spawnStreamWriter.WriteLine("Side=" + mission.Side); + spawnStreamWriter.WriteLine("BuildOffAlly=" + mission.BuildOffAlly); + + UserINISettings.Instance.Difficulty.Value = difficulty; + + spawnStreamWriter.WriteLine("DifficultyModeHuman=" + (mission.PlayerAlwaysOnNormalDifficulty ? "1" : difficulty.ToString())); + spawnStreamWriter.WriteLine("DifficultyModeComputer=" + GetComputerDifficulty(difficulty)); + + spawnStreamWriter.WriteLine(); + spawnStreamWriter.WriteLine(); + spawnStreamWriter.WriteLine(); + } + + var difficultyIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, DifficultyIniPaths[difficulty])); + + if (copyMapsToSpawnmapINI) + { + var mapIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, mission.Scenario)); + IniFile.ConsolidateIniFiles(mapIni, difficultyIni); + mapIni = AppendVariableBinding(mapIni, mission); + mapIni.WriteIniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, "spawnmap.ini")); + } + + UserINISettings.Instance.Difficulty.Value = difficulty; + UserINISettings.Instance.SaveSettings(); + } + private int GetComputerDifficulty(int selected) => + Math.Abs(selected - 2); + + private IniFile AppendVariableBinding(IniFile src, Mission mission) + { + IniFile mapIni = src; + + // For locals, we just put the variable in the [VariableNames] section and set the default value + foreach (var local in mission.LocalBindings) + mapIni.SetStringValue("VariableNames", local.Value.ToString(), local.Key + "," + Variables[local.Key]); + + if (mission.HomeCell != string.Empty) + { + // Syntax for homecell change should be either "Variable:Waypoint" or "Variable|Value:Waypoint,Value:Waypoint,..." + + int index = mission.HomeCell.IndexOf('|'); + + if (index != -1) + { + string variable = mission.HomeCell.Substring(0, index); + string[] values = mission.HomeCell.Substring(index + 1).Split(','); + + foreach (var v in values) + { + string[] parts = v.Split(':'); + + if (parts[0] == Variables[variable].ToString()) + { + if (parts.Length == 2) + mapIni.SetStringValue("Basic", "HomeCell", parts[1]); + else + Logger.Log("Incorrect syntax for HomeCell flag on mission " + mission.InternalName + ": " + v); + } + } + } + else + { + string[] parts = mission.HomeCell.Split(':'); + + if (Variables[parts[0]] > 0) + { + if (parts.Length == 2) + mapIni.SetStringValue("Basic", "HomeCell", parts[1]); + else + Logger.Log("Incorrect syntax for HomeCell flag on mission " + mission.InternalName + ": " + mission.HomeCell); + } + } + } + + // For globals, we need to make a trigger to set all the globals properly + if (mission.GlobalBindings.Count > 0) + { + string action = ""; + int count = 0; + + foreach (var global in mission.GlobalBindings) { - if(!Missions.Exists(m => m.InternalName == binding.Key)) + if (Variables[global.Key] > 0) { - Logger.Log("Mission " + mission.InternalName + " has mission unlock defined for non-existant mission: " + binding.Key); - mission.MissionUnlocks.Remove(binding); - continue; + action = action + ",28,0," + global.Value.ToString() + ",0,0,0,0,A"; + count++; } } + + // If none of the variables need to be set, skip to return the map ini before we write a nonsense trigger + if (count < 1) + return mapIni; + + + IniFile bindings = new IniFile(); + + bindings.SetStringValue("Triggers", "XNACLIENT", "Neutral,,Bind Client Variables,0,1,1,1,0"); + bindings.SetStringValue("Events", "XNACLIENT", "1,13,0,0"); + bindings.SetStringValue("Tags", "XNACLIENTTag", "0,Used by XNA Client,XNACLIENT"); + bindings.SetStringValue("Actions", "XNACLIENT", count + action); + + // Consolidating in this order should place our trigger as first in the list + IniFile.ConsolidateIniFiles(bindings, mapIni); + return bindings; } + + return mapIni; } } } diff --git a/DXMainClient/Domain/Singleplayer/CareerHandler.cs b/DXMainClient/Domain/Singleplayer/CareerHandler.cs index 77f9aa118..0cc1ea174 100644 --- a/DXMainClient/Domain/Singleplayer/CareerHandler.cs +++ b/DXMainClient/Domain/Singleplayer/CareerHandler.cs @@ -41,6 +41,7 @@ public static void ReadCareerData(List missions, Dictionary(); + for(int i = 0; i < 10; i++) + { + string str = section.GetStringValue("ConfigureVariable" + i, string.Empty); + if (str == string.Empty) + break; + ConfigurableVariables.Add(str); + } + + LocalBindings = ParseVariables(section.GetStringValue("BindLocals", string.Empty)); + GlobalBindings = ParseVariables(section.GetStringValue("BindGlobals", string.Empty)); + LocalUpdates = ParseVariables(section.GetStringValue("LocalUpdates", string.Empty)); + GlobalUpdates = ParseVariables(section.GetStringValue("GlobalUpdates", string.Empty)); } public string InternalName { get; } - public int CD { get; } + public int CD { get; private set; } public int CampaignID { get; } = -1; - public int Side { get; } - public string Scenario { get; } - public string GUIName { get; } - public string UntranslatedGUIName { get; } - public string IconPath { get; } - public string GUIDescription { get; } - public string FinalMovie { get; } - public bool RequiredAddon { get; } - public bool Enabled { get; } - public bool BuildOffAlly { get; } + public int Side { get; private set; } + public string Scenario { get; private set; } + public string GUIName { get; private set; } + public string UntranslatedGUIName { get; private set; } + public string IconPath { get; private set; } + public string GUIDescription { get; private set; } + public string FinalMovie { get; private set; } + public bool RequiredAddon { get; private set; } + public bool Enabled { get; private set; } + public bool BuildOffAlly { get; private set; } public bool PlayerAlwaysOnNormalDifficulty { get; } - - /// - /// Does this mission need to be unlocked by playing another mission? - /// public bool RequiresUnlocking { get; private set; } - - /// - /// Is this mission currently unlocked? - /// public bool IsUnlocked { get; set; } - - /// - /// The best state in which the mission was last completed. - /// public CompletionState Rank { get; set; } + public string HomeCell { get; } + public List ConfigurableVariables { get; } + public Dictionary LocalBindings { get; } + public Dictionary GlobalBindings { get; } + public Dictionary LocalUpdates { get; } + public Dictionary GlobalUpdates { get; } - /// - /// A set of bindings defining conditions which will unlock a given mission. - /// - public List MissionUnlocks { get; private set; } + private Dictionary ParseVariables(string str) + { + if (str == string.Empty) + return new Dictionary(); - /// - /// A set of bindings used to set in-game variables and include ini files in mission maps. - /// - public List PreGameBindings { get; private set; } + Dictionary binds = new Dictionary(); - /// - /// A set of bindings used to update career variables after mission completion. - /// - public List PostGameBindings { get; private set; } + string[] vars = str.Split(','); - private List InitBindings(IniSection section, string prefix) - { - List bindings = new List(); - foreach (KeyValuePair kvp in section.Keys.Where(k => k.Key.StartsWith(prefix))) + foreach (string var in vars) { - string[] parts = kvp.Key.Split("."); - if (parts.Length > 2) + string[] parts = var.Split(':'); + if(parts.Length == 2) { - Logger.Log("Campaign binding key containing more than one /'./' will be skipped: " + kvp.Key); - continue; + binds.Add(parts[0], int.Parse(parts[1])); + } + else + { + Logger.Log(InternalName + " failed trying to parse client variable: " + var); } - CampaignBinding binding = new CampaignBinding(parts[1]); - binding.Bind(kvp.Value); - bindings.Add(binding); } - return bindings; + + return binds; } } }