diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod.XCOM_sln b/MCMBuilderClientTestMod/MCMBuilderClientTestMod.XCOM_sln
new file mode 100644
index 0000000..7c96003
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod.XCOM_sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# XCOM ModBuddy Solution File, Format Version 11.00
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{5DAE07AF-E217-45C1-8DE7-FF99D6011E8A}") = "MCMBuilderClientTestMod", "MCMBuilderClientTestMod\MCMBuilderClientTestMod.x2proj", "{9431E66B-C62F-45C5-AFDB-9F51743A224E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|XCOM 2 = Debug|XCOM 2
+ Default|XCOM 2 = Default|XCOM 2
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9431E66B-C62F-45C5-AFDB-9F51743A224E}.Debug|XCOM 2.ActiveCfg = Debug|XCOM 2
+ {9431E66B-C62F-45C5-AFDB-9F51743A224E}.Debug|XCOM 2.Build.0 = Debug|XCOM 2
+ {9431E66B-C62F-45C5-AFDB-9F51743A224E}.Default|XCOM 2.ActiveCfg = Default|XCOM 2
+ {9431E66B-C62F-45C5-AFDB-9F51743A224E}.Default|XCOM 2.Build.0 = Default|XCOM 2
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComEditor.ini b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComEditor.ini
new file mode 100644
index 0000000..4f83d00
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComEditor.ini
@@ -0,0 +1,2 @@
+[ModPackages]
++ModPackages=MCMBuilderClientTestMod
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComEngine.ini b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComEngine.ini
new file mode 100644
index 0000000..69a2d39
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComEngine.ini
@@ -0,0 +1,6 @@
+[Engine.ScriptPackages]
++NonNativePackages=ModConfigMenuBuilderAPI_1_0_0
++NonNativePackages=MCMBuilderClientTestMod
+
+[UnrealEd.EditorEngine]
++ModEditPackages=ModConfigMenuBuilderAPI_1_0_0
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComGame.ini b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComGame.ini
new file mode 100644
index 0000000..7004743
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComGame.ini
@@ -0,0 +1,2 @@
+[MCMBuilderClientTestMod.X2DownloadableContentInfo_MCMBuilderClientTestMod]
+DLCIdentifier="MCMBuilderClientTestMod"
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComJsonConfigManager.ini b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComJsonConfigManager.ini
new file mode 100644
index 0000000..48074c5
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComJsonConfigManager.ini
@@ -0,0 +1,5 @@
+[MCMBuilderClientTestModConfigManager JsonConfig_ManagerDefault]
++ConfigProperties = {"HUNGRY":{"Value":"true"}}
++ConfigProperties = {"HUNGER_SCALE":{"Value":"1"}}
++ConfigProperties = {"HUNGER_SCALE_NERD":{"Value":"0.1"}}
++ConfigProperties = {"FOOD":{"Value":"Apple"}}
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComMCMBuilder.ini b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComMCMBuilder.ini
new file mode 100644
index 0000000..0ce5bc3
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Config/XComMCMBuilder.ini
@@ -0,0 +1,19 @@
+[ModConfigMenuBuilder.MCM_Builder_Screen]
++MCMBuilder="MCMBuilderClientTestMod"
+
+[MCMBuilderClientTestMod JsonConfig_MCM_Builder]
++MCMPages = { \\
+ "TESTMOD_SETTINGS_PAGE":{ \\
+ "SaveConfigManager": "MCMBuilderClientTestModConfigManager",\\
+ "EnableResetButton": "true", \\
+ "TESTMOD_SETTINGS_GROUP_1":{ \\
+ "HUNGRY": { "Type": "Checkbox" }, \\
+ "HUNGER_SCALE_NERD": { "Type": "Slider", "SliderMin": "0.0", "SliderMax": "1.0", "SliderStep":"0.1" }, \\
+ "HUNGER_SCALE": { "Type": "Spinner", "Options": "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }, \\
+ "FOOD": { "Type": "Dropdown", "Options": "Apple, Chocolate, Burger" }, \\
+ }, \\
+ "TESTMOD_SETTINGS_GROUP_2":{ \\
+ "A_LABEL": { "Type": "Label" }, \\
+ }, \\
+ }, \\
+}
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Localization/ModConfigMenu.int b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Localization/ModConfigMenu.int
new file mode 100644
index 0000000..0b813dd
Binary files /dev/null and b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Localization/ModConfigMenu.int differ
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/MCMBuilderClientTestMod.x2proj b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/MCMBuilderClientTestMod.x2proj
new file mode 100644
index 0000000..16a2357
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/MCMBuilderClientTestMod.x2proj
@@ -0,0 +1,59 @@
+
+
+
+ 91050dba-9f3c-4243-90d9-f6bdef9ad716
+ MCMBuilderClientTestMod
+ Description of My XCOM 2 Mod.
+ 0
+ MCMBuilderClientTestMod
+ MCMBuilderClientTestMod
+
+
+ bin\Debug\
+
+
+
+
+
+
+
+
+
+
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+
+
+
+
+
+ Content
+
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+
+
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/ModPreview.jpg b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/ModPreview.jpg
new file mode 100644
index 0000000..5bdd8d8
Binary files /dev/null and b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/ModPreview.jpg differ
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/ReadMe.txt b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/ReadMe.txt
new file mode 100644
index 0000000..fc6b988
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/ReadMe.txt
@@ -0,0 +1 @@
+You created an XCOM 2 Mod Project!
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/Helper.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/Helper.uc
new file mode 100644
index 0000000..edb1027
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/Helper.uc
@@ -0,0 +1,15 @@
+//-----------------------------------------------------------
+// Class: Helper
+// Author: Musashi
+//
+//-----------------------------------------------------------
+
+
+class Helper extends Object;
+
+const CONFIG_MANAGER = "MCMBuilderClientTestModConfigManager";
+
+public static final function JsonConfig_ManagerInterface GetConfig()
+{
+ return class'ConfigFactory'.static.GetConfigManager(CONFIG_MANAGER);
+}
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/X2DownloadableContentInfo_MCMBuilderClientTestMod.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/X2DownloadableContentInfo_MCMBuilderClientTestMod.uc
new file mode 100644
index 0000000..7882bd0
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/X2DownloadableContentInfo_MCMBuilderClientTestMod.uc
@@ -0,0 +1,25 @@
+//---------------------------------------------------------------------------------------
+// FILE: XComDownloadableContentInfo_MCMBuilderClientTestMod.uc
+//
+// Use the X2DownloadableContentInfo class to specify unique mod behavior when the
+// player creates a new campaign or loads a saved game.
+//
+//---------------------------------------------------------------------------------------
+// Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
+//---------------------------------------------------------------------------------------
+
+class X2DownloadableContentInfo_MCMBuilderClientTestMod extends X2DownloadableContentInfo;
+
+static function bool AbilityTagExpandHandler(string InString, out string OutString)
+{
+ local string PossibleValue;
+
+ PossibleValue = class'Helper'.static.GetConfig().GetConfigTagValue(InString);
+ if (PossibleValue != "")
+ {
+ OutString = PossibleValue;
+ return true;
+ }
+
+ return false;
+}
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/X2EventListener_TestMod.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/X2EventListener_TestMod.uc
new file mode 100644
index 0000000..3fe68c0
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/MCMBuilderClientTestMod/Classes/X2EventListener_TestMod.uc
@@ -0,0 +1,175 @@
+class X2EventListener_TestMod extends X2EventListener;
+
+static function array CreateTemplates()
+{
+ local array Templates;
+
+ Templates.AddItem(CreateListenerTemplate_MCMBuilderListener());
+
+ return Templates;
+}
+
+static function CHEventListenerTemplate CreateListenerTemplate_MCMBuilderListener()
+{
+ local CHEventListenerTemplate Template;
+
+ `CREATE_X2TEMPLATE(class'CHEventListenerTemplate', Template, 'MCMBuilderClientTestMod_MCMBuilderListener');
+
+ Template.RegisterInStrategy = true;
+ Template.RegisterInTactical = true;
+
+ Template.AddCHEvent('MCM_ButtonClick', OnMCM_ButtonClick, ELD_Immediate);
+ Template.AddCHEvent('MCM_ChangeHandler', OnMCM_ChangeHandler, ELD_Immediate);
+ Template.AddCHEvent('MCM_SaveHandler', OnMCM_SaveHandler, ELD_Immediate);
+ Template.AddCHEvent('MCM_SaveButtonClicked', OnMCM_SaveButtonClicked, ELD_Immediate);
+ Template.AddCHEvent('MCM_ConfigSaved', OnMCM_ConfigSaved, ELD_Immediate);
+ Template.AddCHEvent('MCM_ResetButtonClicked', OnMCM_ResetButtonClicked, ELD_Immediate);
+ Template.AddCHEvent('MCM_ConfigResetted', OnMCM_ConfigResetted, ELD_Immediate);
+
+ return Template;
+}
+
+
+static function EventListenerReturn OnMCM_ButtonClick(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ if (EventSource != none)
+ {
+ `LOG(default.class @ GetFuncName() @ EventSource,, 'MCMBuilderClientTestMod');
+ }
+
+ return ELR_NoInterrupt;
+}
+
+static function EventListenerReturn OnMCM_ChangeHandler(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ local MCM_Builder_Interface Builder;
+ local JsonObject Tuple;
+
+ Tuple = JsonObject(EventSource);
+ Builder = MCM_Builder_Interface(Tuple.GetObject("MCMBuilder"));
+
+ if (Builder != none && Builder.GetBuilderName() == "MCMBuilderClientTestMod")
+ {
+ `LOG(default.class @ GetFuncName() @
+ Tuple.GetObject("MCMBuilder") @
+ Tuple.GetStringValue("SettingLabel") @
+ Tuple.GetStringValue("SettingValue") @
+ Tuple.GetBoolValue("bOverrideDefaultHandler")
+ ,, 'MCMBuilderClientTestMod');
+
+ MakePopup(EventName, Tuple.GetStringValue("SettingLabel") $ "(" $ Tuple.GetStringValue("SettingName") $ ")" $ ":" @ Tuple.GetStringValue("SettingValue"));
+ }
+
+ return ELR_NoInterrupt;
+}
+
+
+static function EventListenerReturn OnMCM_SaveHandler(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ local MCM_Builder_Interface Builder;
+ local JsonObject Tuple;
+
+ Tuple = JsonObject(EventSource);
+ Builder = MCM_Builder_Interface(Tuple.GetObject("MCMBuilder"));
+
+ if (Builder != none && Builder.GetBuilderName() == "MCMBuilderClientTestMod")
+ {
+ `LOG(default.class @ GetFuncName() @
+ Tuple.GetObject("MCMBuilder") @
+ Tuple.GetStringValue("SettingLabel") @
+ Tuple.GetStringValue("SettingValue") @
+ Tuple.GetBoolValue("bOverrideDefaultHandler")
+ ,, 'MCMBuilderClientTestMod');
+
+ MakePopup(EventName, Tuple.GetStringValue("SettingLabel") $ "(" $ Tuple.GetStringValue("SettingName") $ ")" $ ":" @ Tuple.GetStringValue("SettingValue"));
+ }
+
+ return ELR_NoInterrupt;
+}
+
+static function EventListenerReturn OnMCM_SaveButtonClicked(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ local MCM_Builder_Interface Builder;
+ local JsonObject Tuple;
+
+ Tuple = JsonObject(EventSource);
+ Builder = MCM_Builder_Interface(Tuple.GetObject("MCMBuilder"));
+
+ `LOG(default.class @ GetFuncName() @
+ Builder @
+ Tuple.GetBoolValue("bOverrideDefaultHandler")
+ ,, 'MCMBuilderClientTestMod');
+
+ if (Builder != none && Builder.GetBuilderName() == "MCMBuilderClientTestMod")
+ {
+ MakePopup(EventName, "bOverrideDefaultHandler:" @ Tuple.GetBoolValue("bOverrideDefaultHandler"));
+ }
+
+ return ELR_NoInterrupt;
+}
+
+static function EventListenerReturn OnMCM_ConfigSaved(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ local MCM_Builder_Interface Builder;
+
+ Builder = MCM_Builder_Interface(EventSource);
+
+ if (Builder != none && Builder.GetBuilderName() == "MCMBuilderClientTestMod")
+ {
+ `LOG(default.class @ GetFuncName() @ Builder.GetBuilderName() @ Builder,, 'MCMBuilderClientTestMod');
+ MakePopup(EventName);
+ }
+
+ return ELR_NoInterrupt;
+}
+
+static function EventListenerReturn OnMCM_ResetButtonClicked(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ local MCM_Builder_Interface Builder;
+
+ Builder = MCM_Builder_Interface(EventSource);
+
+ if (Builder != none && Builder.GetBuilderName() == "MCMBuilderClientTestMod")
+ {
+ `LOG(default.class @ GetFuncName() @ EventSource,, 'MCMBuilderClientTestMod');
+ MakePopup(EventName);
+ }
+
+ return ELR_NoInterrupt;
+}
+
+static function EventListenerReturn OnMCM_ConfigResetted(Object EventData, Object EventSource, XComGameState GameState, Name EventName, Object CallbackData)
+{
+ local MCM_Builder_Interface Builder;
+
+ Builder = MCM_Builder_Interface(EventSource);
+
+ if (Builder != none && Builder.GetBuilderName() == "MCMBuilderClientTestMod")
+ {
+ `LOG(default.class @ GetFuncName() @ EventSource,, 'MCMBuilderClientTestMod');
+ MakePopup(EventName);
+ }
+
+ return ELR_NoInterrupt;
+}
+
+simulated static function MakePopup(Name EventName, optional string Text = "")
+{
+ local TDialogueBoxData kDialogData;
+ local JsonConfig_ManagerInterface ConfigManager;
+
+ ConfigManager = class'Helper'.static.GetConfig();
+
+ kDialogData.eType = eDialog_Normal;
+ kDialogData.strTitle = string(EventName);
+ kDialogData.strText = Text != "" ? Text :
+ "HUNGRY:" @ ConfigManager.GetConfigBoolValue("HUNGRY") $ "\n" $
+ "HUNGER_SCALE:" @ ConfigManager.GetConfigIntValue("HUNGER_SCALE") $ "\n" $
+ "HUNGER_SCALE_NERD:" @ ConfigManager.GetConfigFloatValue("HUNGER_SCALE_NERD") $ "\n" $
+ "FOOD:" @ ConfigManager.GetConfigStringValue("FOOD");
+ //kDialogData.fnCallback = OKClickedGeneric;
+
+ kDialogData.strAccept = class'UIUtilities_Text'.default.m_strGenericContinue;
+
+ `PRESBASE.UIRaiseDialog(kDialogData);
+}
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/ConfigFactory.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/ConfigFactory.uc
new file mode 100644
index 0000000..a1c4cc0
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/ConfigFactory.uc
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------
+// Class: ConfigFactory
+// Author: Musashi
+// DO NOT MAKE ANY CHANGES TO THIS CLASS
+//-----------------------------------------------------------
+class ConfigFactory extends Object;
+
+static function JsonConfig_ManagerInterface GetConfigManager(string ManagerName)
+{
+ local MCM_Builder_SingletonFactoryInterface SingletonFactoryInterface;
+ local object SingletonFactoryCDO;
+
+ SingletonFactoryCDO = class'XComEngine'.static.GetClassDefaultObjectByName('MCM_Builder_SingletonFactory');
+ SingletonFactoryInterface = MCM_Builder_SingletonFactoryInterface(SingletonFactoryCDO);
+ return SingletonFactoryInterface.static.GetManagerInstance(ManagerName);
+}
+
+static function MCM_Builder_Interface GetMCMBuilder(string BuilderName)
+{
+ local MCM_Builder_SingletonFactoryInterface SingletonFactoryInterface;
+ local object SingletonFactoryCDO;
+
+ SingletonFactoryCDO = class'XComEngine'.static.GetClassDefaultObjectByName('MCM_Builder_SingletonFactory');
+ SingletonFactoryInterface = MCM_Builder_SingletonFactoryInterface(SingletonFactoryCDO);
+ return SingletonFactoryInterface.static.GetMCMBuilderInstance(BuilderName);
+}
\ No newline at end of file
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/JsonConfig_ManagerInterface.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/JsonConfig_ManagerInterface.uc
new file mode 100644
index 0000000..42fdbca
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/JsonConfig_ManagerInterface.uc
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------
+// Interface: JsonConfig_ManagerInterface
+// Author: Musashi
+// DO NOT MAKE ANY CHANGES TO THIS CLASS
+//-----------------------------------------------------------
+interface JsonConfig_ManagerInterface;
+
+static public function JsonConfig_ManagerInterface GetConfigManager(string InstanceName, optional bool bHasDefaultConfig = true);
+
+static public function JsonConfig_ManagerInterface GetDefaultConfigManager();
+
+public function SerializeAndSaveConfig();
+
+public function bool HasConfigProperty(coerce string PropertyName, optional string Namespace);
+
+public function SetConfigString(string PropertyName, coerce string Value);
+
+public function int GetConfigIntValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function float GetConfigFloatValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function name GetConfigNameValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function int GetConfigByteValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function bool GetConfigBoolValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigIntArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigFloatArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigNameArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function vector GetConfigVectorValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigStringArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function WeaponDamageValue GetConfigDamageValue(coerce string PropertyName, optional string Namespace);
+
+public function string GetConfigStringValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function string GetConfigTagValue(coerce string PropertyName, optional string Namespace);
+
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_Interface.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_Interface.uc
new file mode 100644
index 0000000..d2df583
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_Interface.uc
@@ -0,0 +1,20 @@
+//-----------------------------------------------------------
+// Interface: MCM_Builder_Interface
+// Author: Musashi
+//
+//-----------------------------------------------------------
+
+
+interface MCM_Builder_Interface;
+
+static public function MCM_Builder_Interface GetMCMBuilder(string InstanceName);
+
+public function array GetConfig();
+
+public function string GetBuilderName();
+
+public function string LocalizeItem(string Key);
+
+public function SerializeAndSaveBuilderConfig();
+
+public function SerializeConfig();
diff --git a/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_SingletonFactoryInterface.uc b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_SingletonFactoryInterface.uc
new file mode 100644
index 0000000..d5dbad5
--- /dev/null
+++ b/MCMBuilderClientTestMod/MCMBuilderClientTestMod/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_SingletonFactoryInterface.uc
@@ -0,0 +1,11 @@
+//-----------------------------------------------------------
+// Class: MCM_Builder_SingletonFactoryInterface
+// Author: Musashi
+// DO NOT MAKE ANY CHANGES TO THIS CLASS
+//-----------------------------------------------------------
+
+interface MCM_Builder_SingletonFactoryInterface;
+
+static function JsonConfig_ManagerInterface GetManagerInstance(string InstanceName, optional bool bHasDefaultConfig = true);
+
+static function MCM_Builder_Interface GetMCMBuilderInstance(string InstanceName);
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Config/XComEngine.ini b/ModConfigMenu/ModConfigMenu/Config/XComEngine.ini
index 1ca7353..aa16def 100755
--- a/ModConfigMenu/ModConfigMenu/Config/XComEngine.ini
+++ b/ModConfigMenu/ModConfigMenu/Config/XComEngine.ini
@@ -1,8 +1,14 @@
[UnrealEd.EditorEngine]
+EditPackages=ModConfigMenuAPI
++ModEditPackages=ModConfigMenuBuilderAPI_1_0_0
++ModEditPackages=ModConfigMenuBuilder
+
[Engine.ScriptPackages]
+NonNativePackages=ModConfigMenu
++NonNativePackages=ModConfigMenuBuilderAPI_1_0_0
++NonNativePackages=ModConfigMenuBuilder
+
;+NonNativePackages=ModConfigMenuAPI
[Engine.Engine]
diff --git a/ModConfigMenu/ModConfigMenu/Config/XComMCMBuilder.ini b/ModConfigMenu/ModConfigMenu/Config/XComMCMBuilder.ini
new file mode 100644
index 0000000..66296b2
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Config/XComMCMBuilder.ini
@@ -0,0 +1,2 @@
+[MusashisModToolbox.MCM_Builder_Screen]
+VERSION_CFG = 1
diff --git a/ModConfigMenu/ModConfigMenu/ModConfigMenu.x2proj b/ModConfigMenu/ModConfigMenu/ModConfigMenu.x2proj
index 04e7fa0..550f9e6 100755
--- a/ModConfigMenu/ModConfigMenu/ModConfigMenu.x2proj
+++ b/ModConfigMenu/ModConfigMenu/ModConfigMenu.x2proj
@@ -17,6 +17,9 @@ For full details on using this mod, troubleshooting, or even building your own m
+
+ Content
+
Content
@@ -70,6 +73,66 @@ For full details on using this mod, troubleshooting, or even building your own m
Content
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
Content
@@ -169,7 +232,11 @@ For full details on using this mod, troubleshooting, or even building your own m
+
+
+
+
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_OptionsScreen.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_OptionsScreen.uc
index f4c965d..4819a28 100755
--- a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_OptionsScreen.uc
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_OptionsScreen.uc
@@ -99,6 +99,9 @@ simulated function OnInit()
{
`log("MCM Core: hiding soldier guy on main menu for visibility.");
HideSoldierIfMainMenu();
+
+ `LOG(default.class @ GetFuncName() @ "RegisterStrategyListeners",, 'ModConfigMenuBuilder');
+ class'X2EventListenerTemplateManager'.static.RegisterStrategyListeners();
}
}
@@ -108,6 +111,9 @@ simulated function OnRemoved()
{
`log("MCM Core: unhiding soldier guy on main menu for visibility.");
ShowSoldierIfMainMenu();
+
+ `LOG(default.class @ GetFuncName() @ "UnRegisterAllListeners",, 'ModConfigMenuBuilder');
+ class'X2EventListenerTemplateManager'.static.UnRegisterAllListeners();
}
}
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_SettingsPanelFacade.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_SettingsPanelFacade.uc
index 869395f..8821115 100755
--- a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_SettingsPanelFacade.uc
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenu/Classes/MCM_SettingsPanelFacade.uc
@@ -31,6 +31,7 @@ simulated function MCM_SettingsPanelFacade InitSettingsPanelFacade(int _PageID,
Container = _Container;
UiInstance = none;
+ return self;
}
// Internal
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig.uc
new file mode 100644
index 0000000..09bb68e
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig.uc
@@ -0,0 +1,167 @@
+//-----------------------------------------------------------
+// Class: JsonConfiguc
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig extends JsonObject;
+
+struct ObjectKey
+{
+ var string Key;
+ var string ParentKey;
+};
+
+static public final function string GetPropertyName(coerce string PropertyName, optional string Namespace)
+{
+ if (Namespace != "")
+ {
+ PropertyName $= ":" $ Namespace;
+ }
+
+ return PropertyName;
+}
+
+static public function string SanitizeJson(string Json)
+{
+ local string Buffer;
+ local int CountBracketsOpen, CountBracketsClose, CountDoubleQuotes;
+
+ Buffer = Repl(Repl(Repl(Json, "\n", ""), " ", ""), " ", "");
+
+ CountBracketsOpen = CountCharacters(Buffer, "{");
+ CountBracketsClose = CountCharacters(Buffer, "}");
+ CountDoubleQuotes = CountCharacters(Buffer, "\"");
+
+ if (CountBracketsOpen != CountBracketsClose ||
+ InStr(Buffer, "\"{") != INDEX_NONE ||
+ CountDoubleQuotes % 2 != 0)
+ {
+ //`LOG(default.class @ GetFuncName() @ "Warning: invalid json" @ Buffer,, 'ModConfigMenuBuilder');
+ return "";
+ }
+
+ Buffer = LTrimToFirstBracket(Buffer);
+ Buffer = RTrimToFirstBracket(Buffer);
+
+ return Buffer;
+}
+
+static public final function int CountCharacters(coerce string S, string Character)
+{
+ local int Count, Index, Max;
+ local string copy;
+
+ copy = S;
+
+ Max = Len(copy);
+
+ for (Index = 0; Index < Max; Index++)
+ {
+ if (Left(copy, 1) == Character)
+ {
+ Count++;
+ }
+ copy = Right(copy, Len(copy) - 1);
+ }
+
+ return Count;
+}
+
+static public final function string LTrimToFirstBracket(coerce string S)
+{
+ while (Left(S, 1) != "{" && Len(S) > 0)
+ {
+ S = Right(S, Len(S) - 1);
+ }
+ return S;
+}
+static public final function string RTrimToFirstBracket(coerce string S)
+{
+ while (Right(S, 1) != "}" && Len(S) > 0)
+ {
+ S = Left(S, Len(S) - 1);
+ }
+ return S;
+}
+
+static public final function array GetAllObjectKeys(coerce string Str)
+{
+ local array Chunks;
+ local string Chunk, ParentKey, GrandParentKey;
+ local array Keys;
+ local ObjectKey ObjKey, EmptyKey;
+
+ ParentKey = "";
+ GrandParentKey = "";
+
+ Str = Repl(Str, "\":{", "$$$\":{");
+ Str = Repl(Str, "\"}}", "}\"}");
+ Str = Repl(Str, "},", "}\"");
+
+ Chunks = SplitString(Str, "\"", true);
+
+ foreach Chunks(Chunk)
+ {
+ if (InStr(Chunk, "$$$") != INDEX_NONE)
+ {
+ ObjKey = EmptyKey;
+ ObjKey.ParentKey = ParentKey;
+ ObjKey.Key = Repl(Chunk, "$$$", "");
+ Keys.AddItem(ObjKey);
+ }
+
+ if (InStr(Chunk, "{") != INDEX_NONE)
+ {
+ if (ParentKey != "")
+ {
+ GrandParentKey = ParentKey;
+ }
+ ParentKey = ObjKey.Key;
+ }
+
+ // Last level
+ if (InStr(Chunk, "}") != INDEX_NONE && ObjKey.Key == ParentKey)
+ {
+ ParentKey = GrandParentKey;
+ GrandParentKey = "";
+ ObjKey = EmptyKey;
+ }
+ else if (InStr(Chunk, "}") != INDEX_NONE && ParentKey != "")
+ {
+ ParentKey = Keys[Keys.Find('Key', ParentKey)].ParentKey;
+ ObjKey = EmptyKey;
+ }
+ }
+
+ return Keys;
+}
+
+static public final function string GetObjectKey(coerce string S)
+{
+ local int Index, Max, DoubleQuoteUnicode;
+ local string Key;
+ local bool bStart;
+
+ Max = Len(S);
+ DoubleQuoteUnicode = 34;
+
+ for (Index = 0; Index < Max; Index++)
+ {
+ if (Asc(Left(S, 1)) == DoubleQuoteUnicode)
+ {
+ if (bStart)
+ break;
+ if (!bStart)
+ bStart = true;
+ }
+
+ if (bStart && Asc(Left(S, 1)) != DoubleQuoteUnicode)
+ {
+ Key $= Left(S, 1);
+ }
+
+ S = Right(S, Len(S) - 1);
+ }
+
+ return Key;
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Array.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Array.uc
new file mode 100644
index 0000000..a4df003
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Array.uc
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_Array
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig_Array extends Object implements (JsonConfig_Interface);
+
+var protectedwrite array ArrayValue;
+
+public function SetArrayValue(array StringArray)
+{
+ ArrayValue = StringArray;
+}
+
+public function array GetArrayValue()
+{
+ return ArrayValue;
+}
+
+public function string ToString()
+{
+ return Join(ArrayValue, ", ");
+}
+
+public function Serialize(out JsonObject JsonObject, string PropertyName)
+{
+ if (ArrayValue.Length > 0)
+ {
+ JSonObject.SetStringValue(PropertyName, Join(ArrayValue, ", "));
+ }
+}
+
+public function bool Deserialize(out JSonObject Data, string PropertyName)
+{
+ local string UnserializedArrayValue;
+ local array EmptyArray;
+
+ UnserializedArrayValue = Data.GetStringValue(PropertyName);
+ if (UnserializedArrayValue != "")
+ {
+ ArrayValue = SplitString(Repl(Repl(UnserializedArrayValue, " ", ""), " ", ""), ",", true);
+ return true;
+ }
+
+ EmptyArray.Length = 0;
+ ArrayValue = EmptyArray;
+
+ return false;
+}
+
+function static string Join(array StringArray, optional string Delimiter = ",", optional bool bIgnoreBlanks = true)
+{
+ local string Result;
+
+ JoinArray(StringArray, Result, Delimiter, bIgnoreBlanks);
+
+ return Result;
+}
+
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Interface.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Interface.uc
new file mode 100644
index 0000000..873a99e
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Interface.uc
@@ -0,0 +1,12 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_Interface
+// Author: Musashi
+//
+//-----------------------------------------------------------
+
+
+interface JsonConfig_Interface;
+
+public function Serialize(out JsonObject JsonObject, string PropertyName);
+public function bool Deserialize(JSonObject Data, string PropertyName);
+
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Builder.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Builder.uc
new file mode 100644
index 0000000..e613665
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Builder.uc
@@ -0,0 +1,110 @@
+class JsonConfig_MCM_Builder extends JsonConfig implements(MCM_Builder_Interface) config(MCMBuilder) perobjectconfig perobjectlocalized;
+
+struct MCMConfigMapEntry
+{
+ var string PageID;
+ var JsonConfig_MCM_Page MCMConfigPage;
+};
+
+var config array MCMPages;
+var protectedwrite array DeserialzedPagesMap;
+var string BuilderName;
+var array ObjectKeys;
+
+public function array GetConfig()
+{
+ return MCMPages;
+}
+
+static public function MCM_Builder_Interface GetMCMBuilder(string InstanceName)
+{
+ local JsonConfig_MCM_Builder MCMBuilder;
+
+ MCMBuilder = new (none, InstanceName) default.class;
+ MCMBuilder.DeserializeConfig();
+ MCMBuilder.BuilderName = InstanceName;
+
+ return MCM_Builder_Interface(MCMBuilder);
+}
+
+public function string GetBuilderName()
+{
+ return BuilderName;
+}
+
+public function string LocalizeItem(string Key)
+{
+ local string Locale;
+
+ Locale = Localize(name @ default.class, Key, "ModConfigMenu");
+
+ if (InStr(Locale, "?INT?") > 0)
+ {
+ //`LOG(default.class @ GetFuncName() @ "Warning localization not found:" @ Key @ "in" @ name @ default.class,, 'ModConfigMenuBuilder');
+ }
+
+ return Locale;
+}
+
+public function SerializeAndSaveBuilderConfig()
+{
+ SerializeConfig();
+ SaveConfig();
+}
+
+private function DeserializeConfig()
+{
+ local MCMConfigMapEntry MapEntry;
+ local JSonObject JSonObject;
+ local JsonConfig_MCM_Page MCMPage;
+ local ObjectKey ObjKey;
+ local string SanitizedJsonString, SerializedMCMPage;
+ local array LocalMCMPages;
+
+ ////`LOG(default.class @ GetFuncName() @ "found entries:" @ MCMPages.Length,, 'ModConfigMenuBuilder');
+
+ LocalMCMPages = GetConfig();
+
+ foreach LocalMCMPages(SerializedMCMPage)
+ {
+ SanitizedJsonString = SanitizeJson(SerializedMCMPage);
+
+ if (SanitizedJsonString != "")
+ {
+ ObjectKeys = GetAllObjectKeys(SanitizedJsonString);
+ JSonObject = class'JSonObject'.static.DecodeJson(SanitizedJsonString);
+
+ foreach ObjectKeys(ObjKey)
+ {
+ if (JSonObject != none && ObjKey.ParentKey == "")
+ {
+ if (DeserialzedPagesMap.Find('PageID', ObjKey.Key) == INDEX_NONE)
+ {
+ MCMPage = new class'JsonConfig_MCM_Page';
+ if (MCMPage.Deserialize(JSonObject, ObjKey.Key, self))
+ {
+ MapEntry.PageID = ObjKey.Key;
+ MapEntry.MCMConfigPage = MCMPage;
+ DeserialzedPagesMap.AddItem(MapEntry);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+public function SerializeConfig()
+{
+ local MCMConfigMapEntry MapEntry;
+ local JSonObject JSonObject;
+
+ MCMPages.Length = 0;
+
+ foreach DeserialzedPagesMap(MapEntry)
+ {
+ JSonObject = new () class'JsonObject';
+ MapEntry.MCMConfigPage.Serialize(JSonObject, MapEntry.PageID);
+ MCMPages.AddItem(class'JSonObject'.static.EncodeJson(JSonObject));
+ }
+}
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Element.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Element.uc
new file mode 100644
index 0000000..db01e29
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Element.uc
@@ -0,0 +1,93 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_MCM_Element
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig_MCM_Element extends Object;
+
+var string ConfigKey;
+var JsonConfig_MCM_Builder Builder;
+var string SettingName;
+var string Type;
+var string Label;
+var string Tooltip;
+var string SliderMin;
+var string SliderMax;
+var string SliderStep;
+var string ButtonLabel;
+var JsonConfig_Array Options;
+
+public function string GetLabel()
+{
+ if (Label != "")
+ {
+ return Label;
+ }
+
+ return Builder.LocalizeItem(SettingName $ "_LABEL");
+}
+
+public function string GetTooltip()
+{
+ if (Tooltip != "")
+ {
+ return Tooltip;
+ }
+
+ return Builder.LocalizeItem(SettingName $ "_TOOLTIP");
+}
+
+public function Serialize(out JsonObject JsonObject, string PropertyName)
+{
+ local JsonObject JsonSubObject;
+
+ ConfigKey = PropertyName;
+
+ JsonSubObject = new () class'JsonObject';
+ JsonSubObject.SetStringValue("SettingName", ConfigKey);
+ JsonSubObject.SetStringValue("Type", Type);
+ JsonSubObject.SetStringValue("Label", Label);
+ JsonSubObject.SetStringValue("Tooltip", Tooltip);
+ JsonSubObject.SetStringValue("SliderMin", SliderMin);
+ JsonSubObject.SetStringValue("SliderMax", SliderMax);
+ JsonSubObject.SetStringValue("SliderStep", SliderStep);
+ JsonSubObject.SetStringValue("ButtonLabel", ButtonLabel);
+ Options.Serialize(JsonSubObject, "Options");
+
+ JSonObject.SetObject(PropertyName, JsonSubObject);
+}
+
+public function bool Deserialize(JSonObject Data, string PropertyName, JsonConfig_MCM_Builder BuilderParam)
+{
+ local JsonObject ElementJson;
+
+ ConfigKey = PropertyName;
+
+ Options = new class'JsonConfig_Array';
+
+ ElementJson = Data.GetObject(PropertyName);
+ if (ElementJson != none)
+ {
+ Builder = BuilderParam;
+ SettingName = ConfigKey;
+ Type = ElementJson.GetStringValue("Type");
+ Label = ElementJson.GetStringValue("Label");
+ Tooltip = ElementJson.GetStringValue("Tooltip");
+ SliderMin = ElementJson.GetStringValue("SliderMin");
+ SliderMax = ElementJson.GetStringValue("SliderMax");
+ SliderStep = ElementJson.GetStringValue("SliderStep");
+ ButtonLabel = ElementJson.GetStringValue("ButtonLabel");
+ Options.Deserialize(ElementJson, "Options");
+
+ return (Type != "");
+ }
+ return false;
+}
+
+
+defaultproperties
+{
+ Begin Object Class=JsonConfig_Array Name=DefaultJsonConfig_Array
+ End Object
+ Options = DefaultJsonConfig_Array;
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Group.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Group.uc
new file mode 100644
index 0000000..d60ebcb
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Group.uc
@@ -0,0 +1,102 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_MCM_Group
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig_MCM_Group extends Object;
+
+var JsonConfig_MCM_Builder Builder;
+var protectedwrite string GroupName;
+var protectedwrite string GroupLabel;
+var string ConfigKey;
+
+var array Elements;
+var string SaveConfigManager;
+
+public function SetGroupName(string GroupNameParam)
+{
+ GroupName = GroupNameParam;
+}
+
+public function string GetGroupName()
+{
+ if (GroupName != "")
+ {
+ return GroupName;
+ }
+
+ return ConfigKey;
+}
+
+public function SetGroupLabel(string GroupLabelParam)
+{
+ GroupLabel = GroupLabelParam;
+}
+
+public function string GetGroupLabel()
+{
+ if (GroupLabel != "")
+ {
+ return GroupLabel;
+ }
+
+ return Builder.LocalizeItem(ConfigKey $ "_LABEL");
+}
+
+public function Serialize(out JsonObject JsonObject, string PropertyName)
+{
+ local JsonObject JsonSubObject;
+
+ ConfigKey = PropertyName;
+
+ JsonSubObject = new () class'JsonObject';
+ JsonSubObject.SetStringValue("GroupName", GroupName);
+ JsonSubObject.SetStringValue("GroupLabel", GroupLabel);
+ JsonSubObject.SetStringValue("SaveConfigManager", SaveConfigManager);
+
+ JSonObject.SetObject(PropertyName, JsonSubObject);
+}
+
+public function bool Deserialize(JSonObject Data, string PropertyName, JsonConfig_MCM_Builder BuilderParam)
+{
+ local JsonObject GroupJson;
+
+ ConfigKey = PropertyName;
+
+ GroupJson = Data.GetObject(PropertyName);
+ if (GroupJson != none)
+ {
+ GroupName = GroupJson.GetStringValue("GroupName");
+ GroupLabel = GroupJson.GetStringValue("GroupLabel");
+ SaveConfigManager = GroupJson.GetStringValue("SaveConfigManager");
+
+ if (SaveConfigManager != "")
+ {
+
+ }
+
+ Builder = BuilderParam;
+ DeserializeElements(GroupJson);
+
+ return true;
+ }
+ return false;
+}
+
+private function DeserializeElements(JsonObject GroupJson)
+{
+ local JsonConfig_MCM_Element Element;
+ local ObjectKey ObjKey;
+
+ foreach Builder.ObjectKeys(ObjKey)
+ {
+ if (ObjKey.ParentKey == ConfigKey)
+ {
+ Element = new class'JsonConfig_MCM_Element';
+ if(Element.Deserialize(GroupJson, ObjKey.Key, Builder))
+ {
+ Elements.AddItem(Element);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Page.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Page.uc
new file mode 100644
index 0000000..c5a0310
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_MCM_Page.uc
@@ -0,0 +1,106 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_MCM_Page
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig_MCM_Page extends Object;
+
+var JsonConfig_MCM_Builder Builder;
+var int MCMPageId;
+var string ConfigKey;
+var string PageTitle;
+var string TabLabel;
+var string EnableResetButton;
+var string SaveConfigManager;
+var array Groups;
+
+public function bool ShouldEnableResetButton()
+{
+ return bool(EnableResetButton);
+}
+
+public function SetPageTitle(string PageTitleParam)
+{
+ PageTitle = PageTitleParam;
+}
+
+public function string GetPageTitle()
+{
+ if (PageTitle != "")
+ {
+ return PageTitle;
+ }
+
+ return Builder.LocalizeItem(ConfigKey $ "_TITLE");
+}
+
+public function SetTabLabel(string TabLabelParam)
+{
+ TabLabel = TabLabelParam;
+}
+
+public function string GetTabLabel()
+{
+ if (TabLabel != "")
+ {
+ return TabLabel;
+ }
+
+ return Builder.LocalizeItem(ConfigKey $ "_LABEL");
+}
+
+public function Serialize(out JsonObject JsonObject, string PropertyName)
+{
+ local JsonObject JsonSubObject;
+
+ ConfigKey = PropertyName;
+
+ JsonSubObject = new () class'JsonObject';
+ JsonSubObject.SetStringValue("PageTitle", PageTitle);
+ JsonSubObject.SetStringValue("TabLabel", TabLabel);
+ JsonSubObject.SetStringValue("EnableResetButton", EnableResetButton);
+ JsonSubObject.SetStringValue("SaveConfigManager", SaveConfigManager);
+
+ JSonObject.SetObject(PropertyName, JsonSubObject);
+}
+
+public function bool Deserialize(JSonObject Data, string PropertyName, JsonConfig_MCM_Builder BuilderParam)
+{
+ local JsonObject PageJson;
+
+ ConfigKey = PropertyName;
+
+ PageJson = Data.GetObject(PropertyName);
+ if (PageJson != none)
+ {
+ PageTitle = PageJson.GetStringValue("PageTitle");
+ TabLabel = PageJson.GetStringValue("TabLabel");
+ EnableResetButton = PageJson.GetStringValue("EnableResetButton");
+ SaveConfigManager = PageJson.GetStringValue("SaveConfigManager");
+ Builder = BuilderParam;
+
+ DeserializeGroups(PageJson);
+
+ return true;
+ }
+
+ return false;
+}
+
+private function DeserializeGroups(JsonObject PageJson)
+{
+ local JsonConfig_MCM_Group Group;
+ local ObjectKey ObjKey;
+
+ foreach Builder.ObjectKeys(ObjKey)
+ {
+ if (ObjKey.ParentKey == ConfigKey)
+ {
+ Group = new class'JsonConfig_MCM_Group';
+ if(Group.Deserialize(PageJson, ObjKey.Key, Builder))
+ {
+ Groups.AddItem(Group);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Manager.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Manager.uc
new file mode 100644
index 0000000..842e45d
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Manager.uc
@@ -0,0 +1,314 @@
+class JsonConfig_Manager extends JsonConfig implements (JsonConfig_ManagerInterface) config(JsonConfigManager_NullConfig) perobjectconfig;
+
+struct ConfigPropertyMapEntry
+{
+ var string PropertyName;
+ var JsonConfig_TaggedConfigProperty ConfigProperty;
+};
+
+var config array ConfigProperties;
+var protectedwrite array DeserialzedConfigPropertyMap;
+var JsonConfig_Manager DefaultConfigManager;
+
+delegate bool TagFunctionDelegate(name TagFunctionName, JsonConfig_TaggedConfigProperty ConfigProperty, out string TagValue);
+
+
+public function array GetConfig()
+{
+ return ConfigProperties;
+}
+
+static public function JsonConfig_ManagerInterface GetConfigManager(string InstanceName, optional bool bHasDefaultConfig = true)
+{
+ local JsonConfig_Manager ConfigManager;
+
+ ConfigManager = new (none, InstanceName) default.class;
+
+ //`LOG(default.class @ GetFuncName() @ `ShowVar(ConfigManager) @ `ShowVar(InstanceName) @ `ShowVar(default.class) @ `ShowVar(JsonConfig_ManagerInterface(ConfigManager)),, 'ModConfigMenuBuilder');
+
+ ConfigManager.DeserializeConfig();
+
+ if (bHasDefaultConfig)
+ {
+ ConfigManager.SetDefaultConfigManager(InstanceName);
+ }
+
+ return JsonConfig_ManagerInterface(ConfigManager);
+}
+
+public function JsonConfig_ManagerInterface GetDefaultConfigManager()
+{
+ return JsonConfig_ManagerInterface(DefaultConfigManager);
+}
+
+public function SetDefaultConfigManager(string InstanceName)
+{
+ local JsonConfig_ManagerInterface LocalJsonConfig_ManagerInterface;
+ LocalJsonConfig_ManagerInterface = class'JsonConfig_ManagerDefault'.static.GetConfigManager(InstanceName, false);
+ DefaultConfigManager = JsonConfig_Manager(LocalJsonConfig_ManagerInterface);
+ //`LOG(default.class @ GetFuncName() @ `ShowVar(InstanceName) @ `ShowVar(DefaultConfigManager),, 'ModConfigMenuBuilder');
+}
+
+public function SerializeAndSaveConfig()
+{
+ SerializeConfig();
+ SaveConfig();
+}
+
+private function DeserializeConfig()
+{
+ local ConfigPropertyMapEntry MapEntry;
+ local JSonObject JSonObject, JSonObjectProperty;
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+ local string SerializedConfigProperty, PropertyName;
+ local array Sections;
+
+ GetPerObjectConfigSections(self.Class, Sections);
+
+ //`LOG(default.class @ GetFuncName() @ self.Name @ "found entries:" @ ConfigProperties.Length @ Sections[0],, 'ModConfigMenuBuilder');
+
+ foreach ConfigProperties(SerializedConfigProperty)
+ {
+ PropertyName = GetObjectKey(SanitizeJson(SerializedConfigProperty));
+ JSonObject = class'JSonObject'.static.DecodeJson(SanitizeJson(SerializedConfigProperty));
+
+ if (JSonObject != none && PropertyName != "")
+ {
+ JSonObjectProperty = JSonObject.GetObject(PropertyName);
+
+ if (JSonObjectProperty != none &&
+ DeserialzedConfigPropertyMap.Find('PropertyName', PropertyName) == INDEX_NONE)
+ {
+ ConfigProperty = new class'JsonConfig_TaggedConfigProperty';
+ ConfigProperty.ManagerInstance = self;
+ ConfigProperty.Deserialize(JSonObjectProperty);
+ MapEntry.PropertyName = PropertyName;
+ MapEntry.ConfigProperty = ConfigProperty;
+ DeserialzedConfigPropertyMap.AddItem(MapEntry);
+ }
+ }
+ }
+}
+
+private function SerializeConfig()
+{
+ local ConfigPropertyMapEntry MapEntry;
+ local JSonObject JSonObject;
+
+ ConfigProperties.Length = 0;
+
+ foreach DeserialzedConfigPropertyMap(MapEntry)
+ {
+ JSonObject = new () class'JsonObject';
+ JSonObject.SetObject(MapEntry.PropertyName, MapEntry.ConfigProperty.Serialize());
+ ConfigProperties.AddItem(class'JSonObject'.static.EncodeJson(JSonObject));
+ }
+}
+
+public function bool HasConfigProperty(coerce string PropertyName, optional string Namespace)
+{
+ PropertyName = GetPropertyName(PropertyName, Namespace);
+
+ return DeserialzedConfigPropertyMap.Find('PropertyName', PropertyName) != INDEX_NONE;
+}
+
+public function SetConfigString(string PropertyName, coerce string Value)
+{
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+ local ConfigPropertyMapEntry MapEntry;
+
+ if (HasConfigProperty(PropertyName))
+ {
+ ConfigProperty = GetConfigProperty(PropertyName);
+ ConfigProperty.SetValue(Value);
+ }
+ else
+ {
+ ConfigProperty = new class'JsonConfig_TaggedConfigProperty';
+ ConfigProperty.ManagerInstance = self;
+ ConfigProperty.SetValue(Value);
+
+ MapEntry.PropertyName = PropertyName;
+ MapEntry.ConfigProperty = ConfigProperty;
+
+ DeserialzedConfigPropertyMap.AddItem(MapEntry);
+ }
+}
+
+public function int GetConfigIntValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ return int(GetConfigStringValue(PropertyName, TagFunction, Namespace));
+}
+
+public function float GetConfigFloatValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ return float(GetConfigStringValue(PropertyName, TagFunction, Namespace));
+}
+
+public function name GetConfigNameValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ return name(GetConfigStringValue(PropertyName, TagFunction, Namespace));
+}
+
+public function int GetConfigByteValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ return byte(GetConfigStringValue(PropertyName, TagFunction, Namespace));
+}
+
+public function bool GetConfigBoolValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ return bool(GetConfigStringValue(PropertyName, TagFunction, Namespace));
+}
+
+public function array GetConfigIntArray(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ local array StringArray;
+ local string Value;
+ local array IntArray;
+
+ StringArray = GetConfigStringArray(PropertyName, TagFunction, Namespace);
+
+ foreach StringArray(Value)
+ {
+ IntArray.AddItem(int(Value));
+ }
+
+ return IntArray;
+}
+
+public function array GetConfigFloatArray(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ local array StringArray;
+ local string Value;
+ local array FloatArray;
+
+ StringArray = GetConfigStringArray(PropertyName, TagFunction, Namespace);
+
+ foreach StringArray(Value)
+ {
+ FloatArray.AddItem(float(Value));
+ }
+
+ return FloatArray;
+}
+
+public function array GetConfigNameArray(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ local array StringArray;
+ local string Value;
+ local array NameArray;
+
+ StringArray = GetConfigStringArray(PropertyName, TagFunction, Namespace);
+
+ foreach StringArray(Value)
+ {
+ NameArray.AddItem(name(Value));
+ }
+
+ return NameArray;
+}
+
+public function vector GetConfigVectorValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+
+ ConfigProperty = GetConfigProperty(PropertyName);
+
+ if (ConfigProperty != none)
+ {
+ ////`LOG(default.class @ GetFuncName() @ `ShowVar(PropertyName) @ "Value:" @ ConfigProperty.VectorValue.ToString() @ `ShowVar(Namespace),, 'ModConfigMenuBuilder');
+ return ConfigProperty.GetVectorValue();
+ }
+
+ return vect(0, 0, 0);
+}
+
+public function array GetConfigStringArray(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+ local array EmptyArray;
+
+ ConfigProperty = GetConfigProperty(PropertyName, Namespace);
+
+ if (ConfigProperty != none)
+ {
+ ////`LOG(default.class @ GetFuncName() @ `ShowVar(PropertyName) @ "Value:" @ ConfigProperty.ArrayValue.ToString() @ `ShowVar(Namespace),, 'ModConfigMenuBuilder');
+ return ConfigProperty.GetArrayValue();
+ }
+
+ EmptyArray.Length = 0; // Prevent unassigned warning
+
+ return EmptyArray;
+}
+
+public function WeaponDamageValue GetConfigDamageValue(coerce string PropertyName, optional string Namespace)
+{
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+ local WeaponDamageValue Value;
+
+ ConfigProperty = GetConfigProperty(PropertyName, Namespace);
+
+ if (ConfigProperty != none)
+ {
+ Value = ConfigProperty.GetDamageValue();
+ ////`LOG(default.class @ GetFuncName() @ `ShowVar(PropertyName) @ "Value:" @ ConfigProperty.DamageValue.ToString() @ `ShowVar(Namespace),, 'ModConfigMenuBuilder');
+ }
+
+ return Value;
+}
+
+public function string GetConfigStringValue(coerce string PropertyName, optional string TagFunction, optional string Namespace)
+{
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+ local string Value;
+
+ ConfigProperty = GetConfigProperty(PropertyName, Namespace);
+
+ if (ConfigProperty != none)
+ {
+ Value = ConfigProperty.GetValue(TagFunction);
+ ////`LOG(default.class @ GetFuncName() @ `ShowVar(PropertyName) @ `ShowVar(Value) @ `ShowVar(TagFunction) @ `ShowVar(Namespace),, 'ModConfigMenuBuilder');
+ }
+
+ return Value;
+}
+
+public function string GetConfigTagValue(coerce string PropertyName, optional string Namespace)
+{
+ local JsonConfig_TaggedConfigProperty ConfigProperty;
+
+ ConfigProperty = GetConfigProperty(PropertyName, Namespace);
+
+ if (ConfigProperty != none)
+ {
+ return ConfigProperty.GetTagValue();
+ }
+
+ return "";
+}
+
+
+public function JsonConfig_TaggedConfigProperty GetConfigProperty(
+ coerce string PropertyName,
+ optional string Namespace
+)
+{
+ local int Index;
+
+ PropertyName = GetPropertyName(PropertyName, Namespace);
+
+ Index = DeserialzedConfigPropertyMap.Find('PropertyName', PropertyName);
+ if (Index != INDEX_NONE)
+ {
+ return DeserialzedConfigPropertyMap[Index].ConfigProperty;
+ }
+
+ if (DefaultConfigManager != none)
+ {
+ return DefaultConfigManager.GetConfigProperty(PropertyName, Namespace);
+ }
+
+ //`LOG(default.class @ GetFuncName() @ "could not find config property for" @ PropertyName @ DeserialzedConfigPropertyMap.Length,, 'ModConfigMenuBuilder');
+
+ return none;
+}
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_ManagerDefault.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_ManagerDefault.uc
new file mode 100644
index 0000000..6ed972c
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_ManagerDefault.uc
@@ -0,0 +1,8 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_ManagerDefault
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig_ManagerDefault extends JsonConfig_Manager config(JsonConfigManager);
+
+
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_TaggedConfigProperty.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_TaggedConfigProperty.uc
new file mode 100644
index 0000000..fc4df0b
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_TaggedConfigProperty.uc
@@ -0,0 +1,269 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_TaggedConfigProperty
+// Author: Musashi
+// Defines a config entry for a config value with meta information for automatic localization tags
+//-----------------------------------------------------------
+
+class JsonConfig_TaggedConfigProperty extends Object dependson(JsonConfig_Manager);
+
+var JsonConfig_Manager ManagerInstance;
+var protected string Value;
+var protectedwrite JsonConfig_Vector VectorValue;
+var protectedwrite JsonConfig_Array ArrayValue;
+var protectedwrite JsonConfig_WeaponDamageValue DamageValue;
+
+var protected string Namespace;
+var protected string TagFunction;
+var protected string TagParam;
+var protected string TagPrefix;
+var protected string TagSuffix;
+
+var protectedwrite bool bIsVector;
+var protectedwrite bool bIsArray;
+var protectedwrite bool bIsDamageValue;
+
+public function string GetTagParam()
+{
+ local string PropertyRefValue;
+
+ // Check if the tag param is referencing another property value
+ if (ManagerInstance.HasConfigProperty(TagParam))
+ {
+ PropertyRefValue = ManagerInstance.GetConfigStringValue(TagParam);
+
+ if (PropertyRefValue != "")
+ {
+ return PropertyRefValue;
+ }
+ }
+
+ return TagParam;
+}
+
+public function string GetValue(optional string TagFunctionIn)
+{
+ if (TagFunctionIn != "")
+ {
+ return GetTagValueModifiedByTagFunction(TagFunctionIn);
+ }
+
+ return Value;
+}
+
+public function SetValue(string ValueParam)
+{
+ bIsVector = false;
+ bIsArray = false;
+ bIsDamageValue = false;
+ Value = ValueParam;
+}
+
+public function vector GetVectorValue()
+{
+ return VectorValue.GetVectorValue();
+}
+
+public function SetVectorValue(vector VectorParam)
+{
+ bIsVector = true;
+ bIsArray = false;
+ bIsDamageValue = false;
+ VectorValue.SetVectorValue(VectorParam);
+}
+
+public function array GetArrayValue()
+{
+ return ArrayValue.GetArrayValue();
+}
+
+public function SetArrayValue(array ArrayValueParam)
+{
+ bIsVector = false;
+ bIsArray = true;
+ bIsDamageValue = false;
+ ArrayValue.SetArrayValue(ArrayValueParam);
+}
+
+public function WeaponDamageValue GetDamageValue()
+{
+ return DamageValue.GetDamageValue();
+}
+
+public function SetDamageValue(WeaponDamageValue DamageValueParam)
+{
+ bIsVector = false;
+ bIsArray = false;
+ bIsDamageValue = true;
+ DamageValue.SetDamageValue(DamageValueParam);
+}
+
+public function string GetTagFunctionValue()
+{
+ return TagFunction;
+}
+
+public function SetTagFunctionValue(string TagFunctionParam)
+{
+ TagFunction = TagFunctionParam;
+}
+
+public function SetTagParamValue(string TagParamParam)
+{
+ TagParam = TagParamParam;
+}
+
+public function string GetTagParamValue()
+{
+ return TagParam;
+}
+
+public function string GetTagValue()
+{
+ local string TagValue;
+
+ if (bIsVector)
+ {
+ TagValue = VectorValue.ToString();
+ }
+ else if (bIsArray)
+ {
+ TagValue = ArrayValue.ToString();
+ }
+ else if (bIsDamageValue)
+ {
+ DamageValue.ToString();
+ }
+ else
+ {
+ TagValue = Value;
+ }
+
+ if (!bIsVector &&
+ TagFunction != "")
+ {
+ TagValue = GetTagValueModifiedByTagFunction(TagFunction);
+ }
+
+ return TagPrefix $ TagValue $ TagSuffix;
+}
+
+function string GetTagValueModifiedByTagFunction(string TagFunctionIn)
+{
+ local int OutValue;
+ local array LocalArrayValue;
+
+ switch (name(TagFunctionIn))
+ {
+ case 'TagValueToPercent':
+ OutValue = int(float(Value) * 100);
+ break;
+ case 'TagValueToPercentMinusHundred':
+ OutValue = int(float(Value) * 100 - 100);
+ break;
+ case 'TagValueMetersToTiles':
+ OutValue = int(float(Value) * class'XComWorldData'.const.WORLD_METERS_TO_UNITS_MULTIPLIER / class'XComWorldData'.const.WORLD_StepSize);
+ break;
+ case 'TagValueTilesToMeters':
+ OutValue = int(float(Value) * class'XComWorldData'.const.WORLD_StepSize / class'XComWorldData'.const.WORLD_METERS_TO_UNITS_MULTIPLIER);
+ break;
+ case 'TagValueTilesToUnits':
+ OutValue = int(float(Value) * class'XComWorldData'.const.WORLD_StepSize);
+ break;
+ case 'TagValueParamAddition':
+ OutValue = int(float(Value) + float(GetTagParam()));
+ break;
+ case 'TagValueParamMultiplication':
+ OutValue = int(float(Value) * float(GetTagParam()));
+ break;
+ case 'TagArrayValue':
+ LocalArrayValue = GetArrayValue();
+ return LocalArrayValue[int(GetTagParam())];
+ break;
+ default:
+ break;
+ }
+
+ return string(OutValue);
+}
+
+
+function JSonObject Serialize()
+{
+ local JsonObject JsonObject;
+
+ JSonObject = new () class'JsonObject';
+
+ if (bIsArray)
+ {
+ ArrayValue.Serialize(JSonObject, "ArrayValue");
+ }
+ else if (bIsVector)
+ {
+ VectorValue.Serialize(JSonObject, "VectorValue");
+ }
+ else if (bIsDamageValue)
+ {
+ DamageValue.Serialize(JSonObject, "DamageValue");
+ }
+ else
+ {
+ JSonObject.SetStringValue("Value", Value);
+ }
+
+ if (Namespace != "")
+ {
+ JSonObject.SetStringValue("Namespace", Namespace);
+ }
+ if (TagFunction != "")
+ {
+ JSonObject.SetStringValue("TagFunction", TagFunction);
+ }
+ if (TagParam != "")
+ {
+ JSonObject.SetStringValue("TagParam", TagParam);
+ }
+ if (TagPrefix != "")
+ {
+ JSonObject.SetStringValue("TagPrefix", TagPrefix);
+ }
+ if (TagSuffix != "")
+ {
+ JSonObject.SetStringValue("TagSuffix", TagSuffix);
+ }
+
+ return JSonObject;
+}
+
+function Deserialize(JSonObject Data)
+{
+ VectorValue = new class'JsonConfig_Vector';
+ ArrayValue = new class'JsonConfig_Array';
+ DamageValue = new class'JsonConfig_WeaponDamageValue';
+
+ bIsVector = VectorValue.Deserialize(Data, "VectorValue");
+ bIsArray = ArrayValue.Deserialize(Data, "ArrayValue");
+ bIsDamageValue = DamageValue.Deserialize(Data, "DamageValue");
+
+ Value = Data.GetStringValue("Value");
+
+ Namespace = Data.GetStringValue("Namespace");
+ TagFunction = Data.GetStringValue("TagFunction");
+ TagParam = Data.GetStringValue("TagParam");
+ TagPrefix = Data.GetStringValue("TagPrefix");
+ TagSuffix = Data.GetStringValue("TagSuffix");
+}
+
+defaultproperties
+{
+ Begin Object Class=JsonConfig_Vector Name=TaggedDefaultJsonConfig_Vector
+ End Object
+ VectorValue = TaggedDefaultJsonConfig_Vector;
+
+ Begin Object Class=JsonConfig_Array Name=TaggedDefaultJsonConfig_Array
+ End Object
+ ArrayValue = TaggedDefaultJsonConfig_Array;
+
+ Begin Object Class=JsonConfig_WeaponDamageValue Name=TaggedDefaultJsonConfig_WeaponDamageValue
+ End Object
+ DamageValue = TaggedDefaultJsonConfig_WeaponDamageValue;
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Vector.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Vector.uc
new file mode 100644
index 0000000..7194fd8
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_Vector.uc
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_Vector
+// Author: Muasshi
+//
+//-----------------------------------------------------------
+class JsonConfig_Vector extends Object implements(JsonConfig_Interface);
+
+var protectedwrite vector VectorValue;
+
+public function SetVectorValue(vector VectorParam)
+{
+ VectorValue = VectorParam;
+}
+
+public function vector GetVectorValue()
+{
+ return VectorValue;
+}
+
+public function string ToString()
+{
+ return VectorValue.X $ "," $ VectorValue.Y $ "," $ VectorValue.Z;
+}
+
+public function Serialize(out JsonObject JsonObject, string PropertyName)
+{
+ local JsonObject JsonSubObject;
+
+ JsonSubObject = new () class'JsonObject';
+ JsonSubObject.SetIntValue("X", VectorValue.X);
+ JsonSubObject.SetIntValue("Y", VectorValue.X);
+ JsonSubObject.SetIntValue("Z", VectorValue.X);
+
+ JSonObject.SetObject(PropertyName, JsonSubObject);
+}
+
+public function bool Deserialize(JSonObject Data, string PropertyName)
+{
+ local JSonObject VectorJson;
+
+ VectorJson = Data.GetObject(PropertyName);
+ if (VectorJson != none)
+ {
+ VectorValue.X = VectorJson.GetIntValue("X");
+ VectorValue.Y = VectorJson.GetIntValue("Y");
+ VectorValue.Z = VectorJson.GetIntValue("Z");
+ return true;
+ }
+
+ VectorValue.X = 0;
+ VectorValue.Y = 0;
+ VectorValue.Z = 0;
+
+ return false;
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_WeaponDamageValue.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_WeaponDamageValue.uc
new file mode 100644
index 0000000..bb9fb6d
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/JsonConfig_WeaponDamageValue.uc
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------
+// Class: JsonConfig_WeaponDamageValue
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class JsonConfig_WeaponDamageValue extends Object implements (JsonConfig_Interface);
+
+var protectedwrite WeaponDamageValue DamageValue;
+
+public function SetDamageValue(WeaponDamageValue DamageValueParam)
+{
+ DamageValue = DamageValueParam;
+}
+
+public function WeaponDamageValue GetDamageValue()
+{
+ return DamageValue;
+}
+
+public function string ToString()
+{
+ // @TODO make proper damage preview function
+ return (DamageValue.Damage - DamageValue.Spread) $ "-" $ (DamageValue.Damage + DamageValue.Spread);
+}
+
+public function Serialize(out JsonObject JsonObject, string PropertyName)
+{
+ local JsonObject JsonSubObject;
+
+ JsonSubObject = new () class'JsonObject';
+ JsonSubObject.SetIntValue("Damage", DamageValue.Damage);
+ JsonSubObject.SetIntValue("Spread", DamageValue.Spread);
+ JsonSubObject.SetIntValue("PlusOne", DamageValue.PlusOne);
+ JsonSubObject.SetIntValue("Crit", DamageValue.Crit);
+ JsonSubObject.SetIntValue("Pierce", DamageValue.Pierce);
+ JsonSubObject.SetIntValue("Rupture", DamageValue.Rupture);
+ JsonSubObject.SetIntValue("Shred", DamageValue.Shred);
+ JsonSubObject.SetStringValue("Tag", string(DamageValue.Tag));
+ JsonSubObject.SetStringValue("DamageType", string(DamageValue.DamageType));
+
+ JSonObject.SetObject(PropertyName, JsonSubObject);
+}
+
+public function bool Deserialize(JSonObject Data, string PropertyName)
+{
+ local JsonObject DamageValueJson;
+
+ DamageValueJson = Data.GetObject(PropertyName);
+ if (DamageValueJson != none)
+ {
+ DamageValue.Damage = DamageValueJson.GetIntValue("Damage");
+ DamageValue.Spread = DamageValueJson.GetIntValue("Spread");
+ DamageValue.PlusOne = DamageValueJson.GetIntValue("PlusOne");
+ DamageValue.Crit = DamageValueJson.GetIntValue("Crit");
+ DamageValue.Pierce = DamageValueJson.GetIntValue("Pierce");
+ DamageValue.Rupture = DamageValueJson.GetIntValue("Rupture");
+ DamageValue.Shred = DamageValueJson.GetIntValue("Shred");
+ DamageValue.Tag = name(DamageValueJson.GetStringValue("Tag"));
+ DamageValue.DamageType = name(DamageValueJson.GetStringValue("DamageType"));
+ return true;
+ }
+
+ DamageValue.Damage = 0;
+ DamageValue.Spread = 0;
+ DamageValue.PlusOne = 0;
+ DamageValue.Crit = 0;
+ DamageValue.Pierce = 0;
+ DamageValue.Rupture = 0;
+ DamageValue.Shred = 0;
+ DamageValue.Tag = '';
+ DamageValue.DamageType = '';
+
+ return false;
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_Defaults.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_Defaults.uc
new file mode 100644
index 0000000..91308c7
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_Defaults.uc
@@ -0,0 +1,8 @@
+//-----------------------------------------------------------
+// Class: MCMBuilder_Defaults
+// Author: Musashi
+// Dummy for MCM Framework. DO NOT DELETE
+//-----------------------------------------------------------
+class MCM_Builder_Defaults extends Object config(MCMBuilder);
+
+var config int VERSION_CFG;
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_Screen.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_Screen.uc
new file mode 100644
index 0000000..0fba42c
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_Screen.uc
@@ -0,0 +1,453 @@
+//-----------------------------------------------------------
+// Class: MCM_Builder_Screen
+// Author: Musashi
+// Here we marry the MCMBuilder with the MCM Api
+// ATTENTION: The event api is experimental at the moment and doesnt work if the MCM Options are openend in the Shell screen
+// This could be fixed in the Highlander (https://github.com/X2CommunityCore/X2WOTCCommunityHighlander/issues/297) but even then would need the highlander to work properly
+// Maybe it would be better to replace it by an delegate based system.
+// Note the event system isnt necessary for the basic functionality
+//-----------------------------------------------------------
+class MCM_Builder_Screen extends Object config(MCMBuilder);
+
+struct BuilderInstance
+{
+ var int PageId;
+ var JsonConfig_MCM_Builder Builder;
+};
+
+var config int VERSION_CFG;
+var config array MCMBuilder;
+
+var array BuilderInstances;
+
+event OnInit(UIScreen Screen)
+{
+ if (MCM_API(Screen) != none)
+ {
+ MCM_API(Screen).RegisterClientMod(1, 0, ClientModCallback);
+ }
+}
+
+simulated function ClientModCallback(MCM_API_Instance ConfigAPI, int GameMode)
+{
+ local string BuilderName;
+
+ foreach default.MCMBuilder(BuilderName)
+ {
+ BuildMCM(
+ BuilderName,
+ ConfigAPI,
+ GameMode
+ );
+ }
+}
+
+simulated function BuildMCM(
+ string BuilderName,
+ MCM_API_Instance ConfigAPI,
+ int GameMode
+)
+{
+ local MCM_Builder_Interface BuilderInterface;
+ local BuilderInstance Instance;
+ local MCMConfigMapEntry MapEntry;
+ local JsonConfig_MCM_Page MCMPageConfig;
+ local JsonConfig_MCM_Group MCMGroupConfig;
+ local JsonConfig_MCM_Element MCMElementConfig;
+ local JsonConfig_ManagerInterface SaveConfigManager;
+ local MCM_API_SettingsPage Page;
+ local MCM_API_SettingsGroup Group;
+ local name SetttingName;
+
+ BuilderInterface = class'ConfigFactory'.static.GetMCMBuilder(BuilderName);
+ // BuilderInterface = class'MCM_Builder_SingletonFactory'.static.GetMCMBuilderInstance(BuilderName);
+ Instance.Builder = JsonConfig_MCM_Builder(BuilderInterface);
+
+ foreach Instance.Builder.DeserialzedPagesMap(MapEntry)
+ {
+ MCMPageConfig = MapEntry.MCMConfigPage;
+ Page = ConfigAPI.NewSettingsPage(MCMPageConfig.GetPageTitle());
+ Page.SetPageTitle(MCMPageConfig.GetTabLabel());
+
+ if (MCMPageConfig.ShouldEnableResetButton())
+ {
+ Page.EnableResetButton(ResetButtonClicked);
+ }
+
+ MCMPageConfig.MCMPageId = Page.GetPageId();
+ Instance.PageId = Page.GetPageId();
+ BuilderInstances.AddItem(Instance);
+
+ foreach MCMPageConfig.Groups(MCMGroupConfig)
+ {
+ SaveConfigManager = GetConfigManager(MCMPageConfig, MCMGroupConfig);
+
+ Group = Page.AddGroup(name(MCMGroupConfig.GetGroupName()), MCMGroupConfig.GetGroupLabel());
+
+ foreach MCMGroupConfig.Elements(MCMElementConfig)
+ {
+ SetttingName = name(Caps(MCMElementConfig.SettingName));
+
+ switch (MCMElementConfig.Type)
+ {
+ case "Label":
+ Group.AddLabel(
+ SetttingName,
+ MCMElementConfig.GetLabel(),
+ MCMElementConfig.GetTooltip()
+ );
+ break;
+ case "Button":
+ Group.AddButton(
+ SetttingName,
+ MCMElementConfig.GetLabel(),
+ MCMElementConfig.GetTooltip(),
+ MCMElementConfig.ButtonLabel,
+ ButtonClickHandler
+ );
+ break;
+ case "Checkbox":
+ Group.AddCheckbox(
+ SetttingName,
+ MCMElementConfig.GetLabel(),
+ MCMElementConfig.GetTooltip(),
+ SaveConfigManager.GetConfigBoolValue(MCMElementConfig.SettingName),
+ BoolSaveHandler,
+ BoolChangeHandler
+ );
+ break;
+ case "Slider":
+ Group.AddSlider(
+ SetttingName,
+ MCMElementConfig.GetLabel(),
+ MCMElementConfig.GetTooltip(),
+ float(MCMElementConfig.SliderMin),
+ float(MCMElementConfig.SliderMax),
+ float(MCMElementConfig.SliderStep),
+ SaveConfigManager.GetConfigFloatValue(MCMElementConfig.SettingName),
+ FloatSaveHandler,
+ FloatChangeHandler
+ );
+ break;
+ case "Spinner":
+ Group.AddSpinner(
+ SetttingName,
+ MCMElementConfig.GetLabel(),
+ MCMElementConfig.GetTooltip(),
+ MCMElementConfig.Options.GetArrayValue(),
+ SaveConfigManager.GetConfigStringValue(MCMElementConfig.SettingName),
+ StringSaveHandler,
+ StringChangeHandler
+ );
+ break;
+ case "Dropdown":
+ Group.AddDropdown(
+ SetttingName,
+ MCMElementConfig.GetLabel(),
+ MCMElementConfig.GetTooltip(),
+ MCMElementConfig.Options.GetArrayValue(),
+ SaveConfigManager.GetConfigStringValue(MCMElementConfig.SettingName),
+ StringSaveHandler,
+ StringChangeHandler
+ );
+ break;
+ default:
+ //`LOG(default.class @ GetFuncName() @ "unknown MCM element type" @ MCMElementConfig.Type);
+ break;
+ }
+ }
+ }
+
+ Page.ShowSettings();
+ Page.SetSaveHandler(SaveButtonClicked);
+ }
+}
+
+simulated function ButtonClickHandler(MCM_API_Setting Setting)
+{
+ `XEVENTMGR.TriggerEvent('MCM_ButtonClick', Setting, GetBuilder(Setting.GetParentGroup().GetParentPage().GetPageId()), none);
+}
+
+simulated function BoolChangeHandler(MCM_API_Setting Setting, bool SettingValue)
+{
+ ElementChangeHandler(Setting, SettingValue);
+}
+
+simulated function BoolSaveHandler(MCM_API_Setting Setting, bool SettingValue)
+{
+ ElementSaveHandler(Setting, SettingValue);
+}
+
+simulated function FloatChangeHandler(MCM_API_Setting Setting, float SettingValue)
+{
+ ElementChangeHandler(Setting, SettingValue);
+}
+
+simulated function FloatSaveHandler(MCM_API_Setting Setting, float SettingValue)
+{
+ ElementSaveHandler(Setting, SettingValue);
+}
+
+simulated function StringChangeHandler(MCM_API_Setting Setting, string SettingValue)
+{
+ ElementChangeHandler(Setting, SettingValue);
+}
+
+simulated function StringSaveHandler(MCM_API_Setting Setting, string SettingValue)
+{
+ ElementSaveHandler(Setting, SettingValue);
+}
+
+simulated function ElementChangeHandler(MCM_API_Setting Setting, coerce string SettingValue)
+{
+ local JsonObject Tuple;
+
+ Tuple = new class'JsonObject';
+ Tuple.SetStringValue("Id", "MCM_ChangeHandler");
+ Tuple.SetObject("MCMBuilder", GetBuilder(Setting.GetParentGroup().GetParentPage().GetPageId()));
+ Tuple.SetStringValue("SettingValue", SettingValue);
+ Tuple.SetStringValue("SettingName", string(Setting.GetName()));
+ Tuple.SetStringValue("SettingLabel", Setting.GetLabel());
+
+ `LOG(default.class @ GetFuncName() @ Setting @ Setting.GetName() @ Setting.GetLabel() @ Setting.GetSettingType(),, 'ModConfigMenuBuilder');
+
+ `XEVENTMGR.TriggerEvent('MCM_ChangeHandler', Setting, Tuple, none);
+}
+
+simulated function ElementSaveHandler(MCM_API_Setting Setting, coerce string SettingValue)
+{
+ local JsonConfig_MCM_Page Page;
+ local JsonConfig_MCM_Group Group;
+ local JsonConfig_ManagerInterface SaveConfigManager;
+ local JsonObject Tuple;
+ local bool bOverrideDefaultHandler;
+
+ bOverrideDefaultHandler = false;
+
+ Tuple = new class'JsonObject';
+ Tuple.SetStringValue("Id", "MCM_SaveHandler");
+ Tuple.SetObject("MCMBuilder", GetBuilder(Setting.GetParentGroup().GetParentPage().GetPageId()));
+ Tuple.SetStringValue("SettingValue", SettingValue);
+ Tuple.SetBoolValue("bOverrideDefaultHandler", bOverrideDefaultHandler);
+ Tuple.SetStringValue("SettingName", string(Setting.GetName()));
+ Tuple.SetStringValue("SettingLabel",Setting.GetLabel());
+
+ `XEVENTMGR.TriggerEvent('MCM_SaveHandler', Setting, Tuple, none);
+
+ if (!Tuple.GetBoolValue("bOverrideDefaultHandler"))
+ {
+
+ Page = GetPage(Setting.GetParentGroup().GetParentPage().GetPageId());
+ Group = GetGroup(
+ Setting.GetParentGroup().GetParentPage().GetPageId(),
+ Setting.GetParentGroup().GetName()
+ );
+ SaveConfigManager = GetConfigManager(Page, Group);
+ SaveConfigManager.SetConfigString(Caps(string(Setting.GetName())), SettingValue);
+ }
+}
+
+simulated function SaveButtonClicked(MCM_API_SettingsPage Page)
+{
+ local JsonConfig_MCM_Page ConfigPage;
+ local JsonConfig_MCM_Group ConfigGroup;
+ local JsonConfig_Manager SaveConfigManager;
+ local JsonConfig_ManagerInterface Temp;
+ local JsonObject Tuple;
+ local bool bOverrideDefaultHandler;
+ local int Index;
+
+ bOverrideDefaultHandler = false;
+
+ Tuple = new class'JsonObject';
+ Tuple.SetStringValue("Id", "MCM_SaveButtonClicked");
+ Tuple.SetBoolValue("bOverrideDefaultHandler", bOverrideDefaultHandler);
+ Tuple.SetObject("MCMBuilder", GetBuilder(Page.GetPageId()));
+
+ `XEVENTMGR.TriggerEvent('MCM_SaveButtonClicked', Page, Tuple, none);
+
+ if (!Tuple.GetBoolValue("bOverrideDefaultHandler"))
+ {
+ for (Index = 0; Index < Page.GetGroupCount(); Index++)
+ {
+ ConfigGroup = GetGroup(
+ Page.GetPageId(),
+ Page.GetGroupByIndex(Index).GetName()
+ );
+ if (ConfigGroup.SaveConfigManager != "")
+ {
+ Temp = class'MCM_Builder_SingletonFactory'.static.GetManagerInstance(ConfigGroup.SaveConfigManager);
+ SaveConfigManager = JsonConfig_Manager(Temp);
+ SaveConfigManager.SerializeAndSaveConfig();
+ }
+ }
+
+ ConfigPage = GetPage(Page.GetPageId());
+
+ Temp = class'MCM_Builder_SingletonFactory'.static.GetManagerInstance(ConfigPage.SaveConfigManager);
+ Temp.SerializeAndSaveConfig();
+
+ `XEVENTMGR.TriggerEvent('MCM_ConfigSaved', Page, GetBuilder(Page.GetPageId()), none);
+ }
+}
+
+simulated function ResetButtonClicked(MCM_API_SettingsPage Page)
+{
+ local JsonConfig_MCM_Page ConfigPage;
+ local JsonConfig_MCM_Group ConfigGroup;
+ local JsonConfig_ManagerInterface SaveConfigManager, DefaultConfigManager;
+ local int Index;
+ local int SettingIndex;
+ local MCM_API_SettingsGroup Group;
+ local MCM_API_Setting Setting;
+ local MCM_API_Checkbox Checkbox;
+ local MCM_API_Slider Slider;
+ local MCM_API_Spinner Spinner;
+ local MCM_API_Dropdown Dropdown;
+ local JsonObject Tuple;
+ local bool bOverrideDefaultHandler;
+
+ bOverrideDefaultHandler = false;
+
+ Tuple = new class'JsonObject';
+ Tuple.SetStringValue("Id", "ResetButtonClicked");
+ Tuple.SetObject("MCMBuilder", GetBuilder(Page.GetPageId()));
+ Tuple.SetBoolValue("bOverrideDefaultHandler", bOverrideDefaultHandler);
+
+ `XEVENTMGR.TriggerEvent('MCM_ResetButtonClicked', Page, Tuple, none);
+ if (!Tuple.GetBoolValue("bOverrideDefaultHandler"))
+ {
+ ConfigPage = GetPage(Page.GetPageId());
+
+ for (Index = 0; Index < Page.GetGroupCount(); Index++)
+ {
+ Group = Page.GetGroupByIndex(Index);
+
+ ConfigGroup = GetGroup(
+ Page.GetPageId(),
+ Page.GetGroupByIndex(Index).GetName()
+ );
+ SaveConfigManager = GetConfigManager(ConfigPage, ConfigGroup);
+ DefaultConfigManager = SaveConfigManager.GetDefaultConfigManager();
+
+ for (SettingIndex = 0; SettingIndex < Group.GetNumberOfSettings(); SettingIndex++)
+ {
+ Setting = Group.GetSettingByIndex(SettingIndex);
+
+ switch (Setting.GetSettingType())
+ {
+ case eSettingType_Checkbox:
+ Checkbox = MCM_API_Checkbox(Setting);
+ Checkbox.SetValue(DefaultConfigManager.GetConfigBoolValue(Checkbox.GetName()), true);
+ break;
+ case eSettingType_Slider:
+ Slider = MCM_API_Slider(Setting);
+ Slider.SetValue(DefaultConfigManager.GetConfigFloatValue(Slider.GetName()), true);
+ break;
+ case eSettingType_Dropdown:
+ Dropdown = MCM_API_Dropdown(Setting);
+ if (Dropdown == none)
+ {
+ Spinner = MCM_API_Spinner(Setting);
+ Spinner.SetValue(DefaultConfigManager.GetConfigStringValue(Spinner.GetName()), true);
+ }
+ else
+ {
+ Dropdown.SetValue(DefaultConfigManager.GetConfigStringValue(Dropdown.GetName()), true);
+ }
+ break;
+ case eSettingType_Spinner:
+ Spinner = MCM_API_Spinner(Setting);
+ Spinner.SetValue(DefaultConfigManager.GetConfigStringValue(Spinner.GetName()), true);
+ break;
+ }
+ }
+ }
+
+ `XEVENTMGR.TriggerEvent('MCM_ConfigResetted', Page, GetBuilder(Page.GetPageId()), none);
+ }
+}
+
+function JsonConfig_ManagerInterface GetConfigManager(JsonConfig_MCM_Page Page, JsonConfig_MCM_Group Group)
+{
+ if (Group.SaveConfigManager != "")
+ {
+ return class'MCM_Builder_SingletonFactory'.static.GetManagerInstance(Group.SaveConfigManager);
+ }
+ else
+ {
+ return class'MCM_Builder_SingletonFactory'.static.GetManagerInstance(Page.SaveConfigManager);
+ }
+}
+
+simulated public function JsonConfig_MCM_Page GetPage(int PageID)
+{
+ local JsonConfig_MCM_Builder Builder;
+ local MCMConfigMapEntry MCMConfig;
+ local JsonConfig_MCM_Page Page;
+
+
+ Builder = GetBuilder(PageID);
+
+ if (Builder != none)
+ {
+ foreach Builder.DeserialzedPagesMap(MCMConfig)
+ {
+ Page = MCMConfig.MCMConfigPage;
+
+ if (Page.MCMPageId == PageID)
+ {
+ return Page;
+ }
+ }
+ }
+
+ //`LOG(default.class @ GetFuncName() @ Builder @ "could not find MCMConfigPage for" @ PageID,, 'ModConfigMenuBuilder');
+
+ return none;
+}
+
+simulated public function JsonConfig_MCM_Group GetGroup(int PageID, name GroupName)
+{
+ local JsonConfig_MCM_Page Page;
+ local JsonConfig_MCM_Group Group;
+
+ Page = GetPage(PageID);
+
+ foreach Page.Groups(Group)
+ {
+ if (name(Group.GetGroupName()) == GroupName)
+ {
+ return Group;
+ }
+ }
+
+ //`LOG(default.class @ GetFuncName() @ "could not find JsonConfig_MCM_Group for" @ PageID @ GroupName,, 'ModConfigMenuBuilder');
+
+ return none;
+}
+
+simulated public function JsonConfig_MCM_Element GetElement(int PageID, name GroupName, name SettingName)
+{
+ local JsonConfig_MCM_Group Group;
+ local JsonConfig_MCM_Element Element;
+
+ Group = GetGroup(PageID, GroupName);
+
+ foreach Group.Elements(Element)
+ {
+ if (name(Element.SettingName) == SettingName)
+ {
+ return Element;
+ }
+ }
+
+ //`LOG(default.class @ GetFuncName() @ "could not find JsonConfig_MCM_Element for" @ PageID @ GroupName @ SettingName,, 'ModConfigMenuBuilder');
+
+ return none;
+}
+
+simulated function JsonConfig_MCM_Builder GetBuilder(int PageID)
+{
+ return BuilderInstances[BuilderInstances.Find('PageId', PageID)].Builder;
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_ScreenListener.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_ScreenListener.uc
new file mode 100644
index 0000000..8efcaba
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_ScreenListener.uc
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------
+// Class: MCM_Builder_ScreenListener
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class MCM_Builder_ScreenListener extends UIScreenListener;
+
+event OnInit(UIScreen Screen)
+{
+ local MCM_Builder_Screen MCMScreen;
+
+ if (ScreenClass==none)
+ {
+ if (MCM_API(Screen) != none)
+ {
+ ScreenClass = Screen.Class;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ MCMScreen = new class'MCM_Builder_Screen';
+ MCMScreen.OnInit(Screen);
+}
+
+defaultproperties
+{
+ ScreenClass = none;
+}
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_SingletonFactory.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_SingletonFactory.uc
new file mode 100644
index 0000000..a3ac655
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilder/Classes/MCM_Builder_SingletonFactory.uc
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------
+// Class: MCM_Builder_SingletonFactory
+// Author: Musashi
+//
+//-----------------------------------------------------------
+class MCM_Builder_SingletonFactory extends Object implements(MCM_Builder_SingletonFactoryInterface) config(Null);
+
+struct ManagerInstanceCache
+{
+ var string InstanceName;
+ var JsonConfig_Manager Manager;
+};
+
+struct MCMBuilderInstanceCache
+{
+ var string InstanceName;
+ var JsonConfig_MCM_Builder Builder;
+};
+
+// only config for fake singleton
+var config array ManagerInstances;
+var config array MCMBuilderInstances;
+
+static function JsonConfig_ManagerInterface GetManagerInstance(string InstanceName, optional bool bHasDefaultConfig = true)
+{
+ local JsonConfig_ManagerInterface JsonConfigManagerInterface;
+ local ManagerInstanceCache NewManagerInstance;
+ local int Index;
+
+ Index = default.ManagerInstances.Find('InstanceName', InstanceName);
+ if (Index == INDEX_NONE)
+ {
+ JsonConfigManagerInterface = class'JsonConfig_Manager'.static.GetConfigManager(InstanceName, bHasDefaultConfig);
+
+ `LOG(default.class @ GetFuncName() @ "create singleton instance" @ JsonConfigManagerInterface,, 'ModConfigMenuBuilder');
+
+ NewManagerInstance.InstanceName = InstanceName;
+ NewManagerInstance.Manager = JsonConfig_Manager(JsonConfigManagerInterface);
+ default.ManagerInstances.AddItem(NewManagerInstance);
+ return JsonConfig_ManagerInterface(NewManagerInstance.Manager);
+ }
+
+ return JsonConfig_ManagerInterface(default.ManagerInstances[Index].Manager);
+}
+
+static function MCM_Builder_Interface GetMCMBuilderInstance(string InstanceName)
+{
+ local MCM_Builder_Interface BuilderInterface;
+ local MCMBuilderInstanceCache NewBuilderInstance;
+ local int Index;
+
+ Index = default.MCMBuilderInstances.Find('InstanceName', InstanceName);
+ if (Index == INDEX_NONE)
+ {
+ BuilderInterface = class'JsonConfig_MCM_Builder'.static.GetMCMBuilder(InstanceName);
+
+ `LOG(default.class @ GetFuncName() @ "create singleton instance" @ BuilderInterface,, 'ModConfigMenuBuilder');
+
+ NewBuilderInstance.InstanceName = InstanceName;
+ NewBuilderInstance.Builder = JsonConfig_MCM_Builder(BuilderInterface);
+ default.MCMBuilderInstances.AddItem(NewBuilderInstance);
+
+ return MCM_Builder_Interface(NewBuilderInstance.Builder);
+ }
+
+ return MCM_Builder_Interface(default.MCMBuilderInstances[Index].Builder);
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/ConfigFactory.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/ConfigFactory.uc
new file mode 100644
index 0000000..b43e4a6
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/ConfigFactory.uc
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------
+// Class: ConfigFactory
+// Author: Musashi
+// DO NOT MAKE ANY CHANGES TO THIS CLASS
+//-----------------------------------------------------------
+class ConfigFactory extends Object;
+
+static function JsonConfig_ManagerInterface GetConfigManager(string ManagerName)
+{
+ local MCM_Builder_SingletonFactoryInterface SingletonFactoryInterface;
+ local object SingletonFactoryCDO;
+
+ SingletonFactoryCDO = class'XComEngine'.static.GetClassDefaultObjectByName('MCM_Builder_SingletonFactory');
+ SingletonFactoryInterface = MCM_Builder_SingletonFactoryInterface(SingletonFactoryCDO);
+ return SingletonFactoryInterface.static.GetManagerInstance(ManagerName);
+}
+
+static function MCM_Builder_Interface GetMCMBuilder(string BuilderName)
+{
+ local MCM_Builder_SingletonFactoryInterface SingletonFactoryInterface;
+ local object SingletonFactoryCDO;
+
+ SingletonFactoryCDO = class'XComEngine'.static.GetClassDefaultObjectByName('MCM_Builder_SingletonFactory');
+ SingletonFactoryInterface = MCM_Builder_SingletonFactoryInterface(SingletonFactoryCDO);
+ return SingletonFactoryInterface.static.GetMCMBuilderInstance(BuilderName);
+}
\ No newline at end of file
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/JsonConfig_ManagerInterface.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/JsonConfig_ManagerInterface.uc
new file mode 100644
index 0000000..42fdbca
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/JsonConfig_ManagerInterface.uc
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------
+// Interface: JsonConfig_ManagerInterface
+// Author: Musashi
+// DO NOT MAKE ANY CHANGES TO THIS CLASS
+//-----------------------------------------------------------
+interface JsonConfig_ManagerInterface;
+
+static public function JsonConfig_ManagerInterface GetConfigManager(string InstanceName, optional bool bHasDefaultConfig = true);
+
+static public function JsonConfig_ManagerInterface GetDefaultConfigManager();
+
+public function SerializeAndSaveConfig();
+
+public function bool HasConfigProperty(coerce string PropertyName, optional string Namespace);
+
+public function SetConfigString(string PropertyName, coerce string Value);
+
+public function int GetConfigIntValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function float GetConfigFloatValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function name GetConfigNameValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function int GetConfigByteValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function bool GetConfigBoolValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigIntArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigFloatArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigNameArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function vector GetConfigVectorValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function array GetConfigStringArray(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function WeaponDamageValue GetConfigDamageValue(coerce string PropertyName, optional string Namespace);
+
+public function string GetConfigStringValue(coerce string PropertyName, optional string TagFunction, optional string Namespace);
+
+public function string GetConfigTagValue(coerce string PropertyName, optional string Namespace);
+
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_Interface.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_Interface.uc
new file mode 100644
index 0000000..d2df583
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_Interface.uc
@@ -0,0 +1,20 @@
+//-----------------------------------------------------------
+// Interface: MCM_Builder_Interface
+// Author: Musashi
+//
+//-----------------------------------------------------------
+
+
+interface MCM_Builder_Interface;
+
+static public function MCM_Builder_Interface GetMCMBuilder(string InstanceName);
+
+public function array GetConfig();
+
+public function string GetBuilderName();
+
+public function string LocalizeItem(string Key);
+
+public function SerializeAndSaveBuilderConfig();
+
+public function SerializeConfig();
diff --git a/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_SingletonFactoryInterface.uc b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_SingletonFactoryInterface.uc
new file mode 100644
index 0000000..d5dbad5
--- /dev/null
+++ b/ModConfigMenu/ModConfigMenu/Src/ModConfigMenuBuilderAPI_1_0_0/Classes/MCM_Builder_SingletonFactoryInterface.uc
@@ -0,0 +1,11 @@
+//-----------------------------------------------------------
+// Class: MCM_Builder_SingletonFactoryInterface
+// Author: Musashi
+// DO NOT MAKE ANY CHANGES TO THIS CLASS
+//-----------------------------------------------------------
+
+interface MCM_Builder_SingletonFactoryInterface;
+
+static function JsonConfig_ManagerInterface GetManagerInstance(string InstanceName, optional bool bHasDefaultConfig = true);
+
+static function MCM_Builder_Interface GetMCMBuilderInstance(string InstanceName);
\ No newline at end of file
diff --git a/documentation/img/mcmbuilder_api_package.jpg b/documentation/img/mcmbuilder_api_package.jpg
new file mode 100644
index 0000000..ae73e43
Binary files /dev/null and b/documentation/img/mcmbuilder_api_package.jpg differ
diff --git a/documentation/img/mcmbuilder_screenshot.jpg b/documentation/img/mcmbuilder_screenshot.jpg
new file mode 100644
index 0000000..a9207f3
Binary files /dev/null and b/documentation/img/mcmbuilder_screenshot.jpg differ
diff --git a/documentation/img/mcmbuilder_xcomengine_ini.jpg b/documentation/img/mcmbuilder_xcomengine_ini.jpg
new file mode 100644
index 0000000..30301e7
Binary files /dev/null and b/documentation/img/mcmbuilder_xcomengine_ini.jpg differ
diff --git a/documentation/jsonconfigmanager.md b/documentation/jsonconfigmanager.md
new file mode 100644
index 0000000..8d5ef8f
--- /dev/null
+++ b/documentation/jsonconfigmanager.md
@@ -0,0 +1,150 @@
+## JsonConfig Manager
+The JsonConfig Manager introduces a new method of defining config values as json string.
+
+#### Integration
+You will need to add the package
+**ModConfigMenuBuilderAPI_1_0_0** from **ModConfigMenu** to your mod project
+
+
+
+
+
+You mod will be dependend on the ModConfigMenu mod and the funtionality will only work if MCM is active.
+
+#### Usage
+```
+[FancyModManagerName JsonConfig_ManagerDefault]
++ConfigProperties = {"BAR":{"Value":"Foo"}}
+```
+these config values can be accessed like
+```
+local JsonConfig_ManagerInterface ConfigManager;
+
+ConfigManager = class'ConfigFactory'.static.GetConfigManager("FancyModManagerName");
+ConfigManager.GetConfigStringValue("BAR")
+```
+
+The advantage over regular config properties is that they can be accessed by string identifiers which makes automatic localization tag generation and mapping in the MCMBuilder possible.
+
+#### Current type support
+currently supported accessors/types are
+
+```
+string GetConfigStringValue
+int GetConfigIntValue
+float GetConfigFloatValue
+name GetConfigNameValue
+byte GetConfigByteValue
+bool GetConfigBoolValue
+array GetConfigIntArray
+array GetConfigFloatArray
+array GetConfigNameArray
+array GetConfigStringArray
+vector GetConfigVectorValue
+WeaponDamageValue GetConfigDamageValue
+string GetConfigTagValue
+```
+
+more support for common X2 structs is planned.
+
+#### Tag generation
+
+There are a whole bunch of meta attributes you can use for localization tag generation. Here are some example:
+
+```
++ConfigProperties = {"ACTIVIST_DETECTION_MOD":{"Value":"-5", "TagFunction":"TagValueMetersToTiles"}}
++ConfigProperties = {"ACTIVIST_DETECTION_MOD_CIVS":{"Value":"0.5", "TagFunction":"TagValueToPercent", "TagSuffix": "%"}}
++ConfigProperties = {"AGRESSION_CRIT_CHANCE":{"Value":"5", "TagPrefix":"+"}}
++ConfigProperties = {"AGRESSION_SCALE_MAX":{"Value":"6", "TagPrefix":"+", "TagFunction":"TagValueParamMultiplication", "TagParam":"AGRESSION_CRIT_CHANCE"}}
++ConfigProperties = {"AIRSTRIKECHARGES":{"Value":"1"}};
++ConfigProperties = {"AIRSTRIKEDAMAGE":{"DamageValue":{"Damage": 10, "Spread": 2, "Shred": 3, "DamageType":"Explosion"}}}
++ConfigProperties = {"ANARCHIST_CHARGES":{"Value":"1"}}
++ConfigProperties = {"ANARCHIST_CRIT":{"Value":"2", "TagPrefix":"+", "TagSuffix":"%"}}
++ConfigProperties = {"ANARCHIST_CRIT_MAX":{"Value":"50", "TagSuffix":"%"}}
++ConfigProperties = {"ARCTHROWER_ABILITIES":{"ArrayValue":"ArcThrowerStun, EMPulser, ChainLightning"}}
+```
+
+The mapping code in client mods DLCInfo class just looks like this:
+
+```unrealscript
+static function bool AbilityTagExpandHandler(string InString, out string OutString)
+{
+ local string PossibleValue;
+ local JsonConfig_ManagerInterface ConfigManager;
+
+ ConfigManager = class'ConfigFactory'.static.GetConfigManager("FancyModManagerName");
+ PossibleValue = ConfigManager.GetConfigTagValue(InString)
+
+ if (PossibleValue != "")
+ {
+ OutString = PossibleValue;
+ return true;
+ }
+
+ return false;
+}
+```
+For the tag generation add the AbilityTagExpandHandler code snippet above to you DLCInfo file.
+
+#### Complete Example:
+
+Config:
+```
++ConfigProperties = {"MILITIA_AIM":{"Value":"3", "TagPrefix":"+"}}
++ConfigProperties = {"MILITIA_RANGE_MULTIPLIER":{"Value":"0.5", "TagPrefix": "-" , "TagFunction":"TagValueToPercent"}}
++ConfigProperties = {"MILITIA_SIGHT_RADIUS":{"Value":"3", "TagPrefix":"+", "TagSuffix":" tiles", "TagFunction":"TagValueMetersToTiles"}}}
+```
+
+Usage:
+```unrealscript
+// Best to put this in a static helper class somewhere
+static function JsonConfig_ManagerInterface GetJsonConfig()
+{
+ return class'ConfigFactory'.static.GetConfigManager("FancyModManagerName");
+}
+
+static function X2AbilityTemplate Militia()
+{
+ local X2AbilityTemplate Template;
+ local X2Effect_RangeMultiplier RangeEffect;
+ local X2Effect_PersistentStatChange Effect;
+
+ RangeEffect = new class'X2Effect_RangeMultiplier';
+ RangeEffect.RangeMultiplier = GetJsonConfig().GetConfigFloatValue("MILITIA_RANGE_MULTIPLIER");
+ RangeEffect.BuildPersistentEffect(1, true, false, false);
+
+ Template = Passive('APT_Militia', "img:///UILibrary_PerkIcons.UIPerk_Urban_Aim", true, RangeEffect);
+
+ Effect = new class'X2Effect_PersistentStatChange';
+ Effect.AddPersistentStatChange(eStat_Offense, GetJsonConfig().static.GetConfigIntValue("MILITIA_AIM"));
+ Effect.AddPersistentStatChange(eStat_SightRadius, GetJsonConfig.static.GetConfigIntValue("MILITIA_SIGHT_RADIUS"));
+
+ AddSecondaryEffect(Template, Effect);
+
+ return Template;
+}
+```
+
+Localization:
+`LocLongDescription="A militia marksman from one of the Resistance camps we saved. Has aim and sight radius, and weapon range penalties."`
+
+Expanded String:
+`A militia marksman from one of the Resistance camps we saved. Has +1 aim and +2 tiles sight radius, and -50% weapon range penalties.`
+
+#### TagFunctions:
+There are some predefined functions you can use in tag generation and to convert values on the fly:
+
+- TagValueToPercent
+- TagValueToPercentMinusHundred
+- TagValueMetersToTiles
+- TagValueTilesToMeters
+- TagValueTilesToUnits
+- TagValueParamAddition
+- TagValueParamMultiplication
+- TagArrayValue
+
+usage for tag generation:
+`+ConfigProperties = {"MILITIA_RANGE_MULTIPLIER":{"Value":"0.5", "TagFunction":"TagValueToPercent"}}`
+
+usage for converting values in code:
+`ConeMultiTarget.ConeEndDiameter = GetJsonConfig().static.GetConfigIntValue("SPRAY_TILE_WIDTH", "TagValueTilesToUnits");`
diff --git a/documentation/mcm_builder.md b/documentation/mcm_builder.md
new file mode 100644
index 0000000..7e66f6c
--- /dev/null
+++ b/documentation/mcm_builder.md
@@ -0,0 +1,192 @@
+# MCMBuilderClientTestMod
+## MCM Builder
+
+MCM Builder provides a new way of creating MCM Pages.
+Instead of writing the mcm code the pages are completely generated from a json config.
+Lets get right down to it and have a look at this example config:
+
+```
+[ModConfigMenuBuilder.MCM_Builder_Screen]
++MCMBuilder="MCMBuilderClientTestMod"
+
+[MCMBuilderClientTestMod JsonConfig_MCM_Builder]
++MCMPages = { \\
+ "TESTMOD_SETTINGS_PAGE":{ \\
+ "SaveConfigManager": "MCMBuilderClientTestModConfigManager",\\
+ "EnableResetButton": "true", \\
+ "TESTMOD_SETTINGS_GROUP_1":{ \\
+ "HUNGRY": { "Type": "Checkbox" }, \\
+ "HUNGER_SCALE_NERD": { "Type": "Slider", "SliderMin": "0.0", "SliderMax": "1.0", "SliderStep":"0.1" }, \\
+ "HUNGER_SCALE": { "Type": "Spinner", "Options": "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }, \\
+ "FOOD": { "Type": "Dropdown", "Options": "Apple, Chocolate, Burger" }, \\
+ }, \\
+ "TESTMOD_SETTINGS_GROUP_2":{ \\
+ "A_LABEL": { "Type": "Label" }, \\
+ }, \\
+ }, \\
+}
+```
+
+This small config is enough to generate a complete MCM page.
+Lets have a look at the elements in detail.
+
+### Pages and Groups
+
+```
+TESTMOD_SETTINGS_PAGE
+TESTMOD_SETTINGS_GROUP_1
+TESTMOD_SETTINGS_GROUP_2
+```
+are mcm page or group identifiers that are used to for localization mapping:
+`ModConfigMenu.int`
+```
+[MCMBuilderClientTestMod JsonConfig_MCM_Builder]
+
+TESTMOD_SETTINGS_PAGE_TITLE="Test Mod"
+TESTMOD_SETTINGS_PAGE_LABEL="MCMBuilder Test Mod"
+
+TESTMOD_SETTINGS_GROUP_1_LABEL="First settings group"
+TESTMOD_SETTINGS_GROUP_2_LABEL="Second setting group"
+```
+
+There are two special config properties:
+
+`"SaveConfigManager": "MCMBuilderClientTestModConfigManager"`
+
+Use a unique name that will used to create a json config manager instance that will be used to get default config properties and thats responsible to save your properties to the user config directory.
+
+`"EnableResetButton": "true"`
+
+this just tells the builder if you want to display a reset button on your page.
+
+### Elements
+```
+"HUNGRY": { "Type": "Checkbox" }, \\
+"HUNGER_SCALE_NERD": { "Type": "Slider", "SliderMin": "0.0", "SliderMax": "1.0", "SliderStep":"0.1" }, \\
+"HUNGER_SCALE": { "Type": "Spinner", "Options": "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }, \\
+"FOOD": { "Type": "Dropdown", "Options": "Apple, Chocolate, Burger" }, \\
+```
+
+These are the actual mcm elements that are displayed.
+The identifiers (like "HUNGRY") are actual referencing config properties from the JsonConfigManager System.
+
+localization works the same as with pages and groups:
+Just define localization properties following the convention:
+```
+_LABEL
+_TOOLTIP
+```
+e.g.
+```
+HUNGRY_LABEL="Hungry?"
+HUNGRY_TOOLTIP="Are you hungry?"
+```
+
+## Client Side Integration
+You will need to add the package
+**ModConfigMenuBuilderAPI_1_0_0** from **ModConfigMenu** to your mod project
+
+
+
+
+
+```
+[Engine.ScriptPackages]
++NonNativePackages=ModConfigMenuBuilderAPI_1_0_0
++NonNativePackages=MCMBuilderClientTestMod
+
+[UnrealEd.EditorEngine]
++ModEditPackages=ModConfigMenuBuilderAPI_1_0_0
+```
+### MCMBuilder config and localization
+
+Then you need two config files and a localization file:
+(this example assumes you mod safe name is **YourMod**
+
+- `Config/XComJsonConfigManager.ini`
+- `Config/XComMCMBuilder.ini`
+- `Localization/ModConfigMenu.int`
+
+Lets see there contents in detail:
+
+##### `XComMCMBuilder.ini`
+
+As explained in detail above this confi will be used to generate a MCM page
+
+```
+[ModConfigMenuBuilder.MCM_Builder_Screen]
++MCMBuilder="YourModMCMPage1" ;can be any name but have to match below and in the localization file
+
+[YourModMCMPage1 JsonConfig_MCM_Builder]
++MCMPages = { \\
+ "TESTMOD_SETTINGS_PAGE":{ \\
+ "SaveConfigManager": "YourModConfigManager",\\
+ "EnableResetButton": "true", \\
+ "TESTMOD_SETTINGS_GROUP_1":{ \\
+ "HUNGRY": { "Type": "Checkbox" }, \\
+ "HUNGER_SCALE_NERD": { "Type": "Slider", "SliderMin": "0.0", "SliderMax": "1.0", "SliderStep":"0.1" }, \\
+ "HUNGER_SCALE": { "Type": "Spinner", "Options": "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }, \\
+ "FOOD": { "Type": "Dropdown", "Options": "Apple, Chocolate, Burger" }, \\
+ }, \\
+ "TESTMOD_SETTINGS_GROUP_2":{ \\
+ "A_LABEL": { "Type": "Label" }, \\
+ }, \\
+ }, \\
+}
+```
+##### `XComJsonConfigManager.ini`
+
+These are the *default* values you want your mcm elements to initialize with. They will be also used for resetting the values to the default if the Reset Button is clicked.
+
+```
+[YourModConfigManager JsonConfig_ManagerDefault]
++ConfigProperties = {"HUNGRY":{"Value":"true"}}
++ConfigProperties = {"HUNGER_SCALE":{"Value":"1"}}
++ConfigProperties = {"HUNGER_SCALE_NERD":{"Value":"0.1"}}
++ConfigProperties = {"FOOD":{"Value":"Apple"}}
+```
+##### `ModConfigMenu.int`
+
+The localization of your page
+
+```
+[YourModMCMPage1 JsonConfig_MCM_Builder]
+
+TESTMOD_SETTINGS_PAGE_TITLE="Test Mod"
+TESTMOD_SETTINGS_PAGE_LABEL="MCMBuilder Test Mod"
+
+TESTMOD_SETTINGS_GROUP_1_LABEL="First settings group"
+TESTMOD_SETTINGS_GROUP_2_LABEL="Second setting group"
+
+HUNGRY_LABEL="Hungry?"
+HUNGRY_TOOLTIP="Are you hungry?"
+
+HUNGER_SCALE_LABEL="Hunger Scale"
+HUNGER_SCALE_TOOLTIP="On a scale from 1 to 10"
+
+HUNGER_SCALE_NERD_LABEL="Hunger Scale (Nerd)"
+HUNGER_SCALE_NERD_TOOLTIP="On a scale from 0.0 to 1.0"
+
+FOOD_LABEL="Food"
+FOOD_TOOLTIP="Which food you want to eat?"
+
+A_LABEL_LABEL="Label"
+A_LABEL_TOOLTIP="Just a tooltip"
+```
+
+You can access the values in your mod like
+
+```unrealscript
+local JsonConfig_ManagerInterface ConfigManager;
+
+ConfigManager = class'ConfigFactory'.static.GetConfigManager("MCMBuilderClientTestModConfigManager");
+ConfigManager.GetConfigBoolValue("HUNGRY")
+```
+
+It will return the values that the user made in the mcm page or the default settings in case the user hasnt made any changes yet.
+For more information about the config system see the [JsonConfigManager Documentation](jsonconfigmanager.md)
+
+### Thats all!
+The MCMBuilder in ModConfigMenu will take care of the rest.
+
+