--[[
	RavenAcsendant
	25MAY2021
	Anommaly Mod Configuration Menu (Anomaly MCM)


	+Modified version of the Anomaly Options menu created by Tronex. It retains all the same features.
	+Dedicated space for mod options (As of 1_4 this includes easy save game specific options storage)
	+Dynamicly loads options  from mod scripts at main menu load
		simplifying the process and alowing it to be done before a save is loaded
		without needing to edit a game script. There by reducing conflicts.
	
	+Change History:
		1_1 added options table passed by referance
		1_2 Fixed a crash that is actualy still part of ui_options
		1_3 Added overide controls for option tree/branch text see the tutorial section "Parameters of option Tree" at the end of this sctipt
		1_4 Integrated dph-hcl's script for save game specific MCM options. See "Tutorial: Using : dph-hcl's script for save game specific MCM options"
		1_5 Pass value to functors
		1.6.0 	Adopted https://semver.org/spec/v1.0.0.html versioning spec for reasons.
				Far left list sorted alphabeticaly, except MCM will always be at the top to serve as a landing page.
				Added logging utility see instruction in mcm_log.script
				Now pass true to on_option_change callback to make it posible to identify when it is coming from mcm
				MCM tracks a session ID that only increments when game exe is launched. ui_mcm.get_session_id()
				Added assert to identify missing val fields as that tends to result in hard to diagnose errors.
				Added assert to block calling get() while mcm is building the options tree.
				Added keybind widget
					includes a simple conflict list and all keybinds list under mcm's tree.
				Added utility functions to support key binds.
					Tracking of held status of shift controls and alt keys with single functon to read all three and a no modifier state
					Functions to help identify single, double and long presses of a key.
					Provided templated mcm option tables for selecting modifier keys and single/double and long presses that include translation strings.
				Updated documentation at end of this sctipt for all keybind related features.
		1.6.1 	Fixed xrdebug 1040 crash.
		1.6.2 	Added a message for when io.open fails to create a file. 
				Made ui_mcm.script not fail if mcm_log.script is missing.
				Made debug logging default to false as orginaly intended.
		1.6.3	Fixed interferance between doubletap and single press when bonund to same key.
		1.6.4	Missed one place in above fix.			
		1.6.5	Added support for unbound MCM keybinds
					pressing ESC while binding a key will unbind the key, clicking will still cancel the bind process
					-1 is the value for unbound
					unbound keys will not be reported as conflicting
				added mouse4-8 as valid keybinds.
				updated mcm_log to 1.0.2 fixing a crash on quit.
				chnaged the doubletap window to be 200ms. down from 500. this will make mcm keybinds feel more responsive. made it adjustable by increments of 5
				special handeling for key wrappers in the keybinds list
				fixed a bug in dph-hcl's script for save game specific MCM options (HarukaSai)
		1.6.6	actually updated mcm_log to 1.0.2 fixing a crash on quit.	

		1.7.0 	Update to Anomaly 1.5.3 earlier versions of anomaly will not be supported due to the stalker games EULA changes.
				Included support for Catspaw's ui hooks
					Adds new custom functors "ui_hook_functor" and "on_selection_functor", which respectively pass along UI element handlers and a trap for unsaved value changes.
					These functors allow for dynamic customizations to MCM's UI elements at the element container level in response to user interactions.
					See the tutorial section on "UI Functors" for more information.
				Fixed a typo "dispaly_key" changed to "display_key", legacy name aliased for compatablity.
				MCM  get(id) will no longer read values from axr_options for settings that are not part of the curent options table
					lacking a defined value type the data returned was always a string, orphaned settings values are also likely to be garbage
					nil will be returned instead, you need to handel this.
					Mostly this was an issue when addon A was reading addon B's settings and addon B had been uninstalled
					these values will still exist in axr_options and can be read directly if needed.
				Due to degree of code chnages steps have been taken to invalidate any monkey patches of the MCM UI.
					
					

				
				

	
	
	Tronex
	2019/10/12
	Anomlous Options Menu
	
	Features:
		- %100 customizable, support for all kind of xml elements
		- Highlight pending changes
		- Option description support
		- Option presets support
		- Reset button to clear pending changes
		- Script callback on option changes
		- Functors capability to excute on apply or init
		- Precoditions capability to hide/show/execute functors
		
	To get an option value inside other scripts, use: ui_mcm.get(parameter)
	Check the options table here to see the values used
	
	See the tutorial at the bottom for adding or modifying options table
	
--]]

VERSION = "1.7.0"
MCM_DEBUG = axr_main.config:r_value("mcm", "mcm/mcm_log/debug_logging2", 1, false)
local enable_debug_prints = false
local enable_ui_functors = true -- feature killswitch

------------------------------------------------------------
-- Strings and LTX management
------------------------------------------------------------
local ini_pres	   = ini_file("presets\\includes.ltx")
local ini_loc 	   = ini_file_ex("localization.ltx")
local ini_mcm_key  = ini_file_ex("mcm_key_localization.ltx")
local _opt_        = "/"                 -- For axr_options.ltx, don't touch!
local opt_section  = "mcm"           -- For axr_options.ltx, don't touch!
local opt_str      = "ui_mcm_"			 -- Option name: "ui_mcm_(option ID)" -- Option description: "ui_mcm_(option ID)_desc"
local opt_str_menu = "ui_mcm_menu_"		 -- Option menu: "ui_mcm_menu_(option menu ID)"
local opt_str_prst = "ui_mcm_prst_"       -- Option preset: "ui_mcm_prst_(option preset ID)"
local opt_str_lst  = "ui_mcm_lst_"		 -- List/Radio keys: "ui_mcm_lst_(key)"
function cc(path,opt) return (path .. _opt_ .. opt) end

------------------------------------------------------------
-- Utilities
------------------------------------------------------------

local session_id = nil
local m_floor, m_ceil, s_find, s_gsub 	    =	 math.floor, math.ceil, string.find, string.gsub
local clr, clamp, round_idp, str_explode 	=	 utils_xml.get_color, clamp, round_idp, str_explode
local precision                             = 6 -- allowed number of zeros
local width_factor                          = utils_xml.is_widescreen() and 0.8 or 1
local clr_o                                 = GetARGB(255, 250, 150, 75)
local clr_g1                                = GetARGB(255, 170, 170, 170)
local clr_g2                                = GetARGB(255, 200, 200, 200)
local clr_w                                 = GetARGB(255, 255, 255, 255)
local clr_r 								= GetARGB(255, 225, 0, 0)
local clr_tree = {
	[1] = GetARGB(255, 180, 180, 180),
	[2] = GetARGB(255, 180, 180, 180),
	[3] = GetARGB(255, 180, 180, 180),
}

local dbg = nil
function print_dbg(...)
	if (not dbg) and MCM_DEBUG and mcm_log then
		dbg = mcm_log.new("DBG")
		dbg.enabled = MCM_DEBUG
		dbg.continuous = true
	end
	if dbg then
		dbg:log(...)
	end
	
	if enable_debug_prints then
		printf(...)
	end
end

function ui_functors_enabled()
	-- for scripts to explicitly check for support 
	return enable_ui_functors
end

------------------------------------------------------------
-- Options
------------------------------------------------------------
options = {}
local opt_temp     = {} -- stores pending changes
local opt_backup   = {} -- do a backup of original changes for comparison with pendings
local opt_index    = {} -- option index by path, so we can locate an option fast without iterating throw the whole options table
local opt_val      = {} -- option value type by path, to execute proper functions on different type without iterating throw the whole options table
local allowed_type = { -- ignore tables from these types in temp tables
	["check"] = true,  
	["list"] = true,  
	["input"] = true,  
	["radio_h"] = true,  
	["radio_v"] = true,  
	["track"] = true,
	["key_bind"] = true,

	
}
local key_by_path = {}
local paths_by_key = {}
local gathering = false


AddScriptCallback("mcm_option_change")
AddScriptCallback("mcm_option_reset")
AddScriptCallback("mcm_option_restore_default")
AddScriptCallback("mcm_option_discard")


function init_opt_base()
	print_dbg("MCM options reset.")
	get_session_id()
	options = {
		{ 	id= "mcm"        , gr={
				{id = "mcm_about", sh=true    , gr={
					{ id= "slide_mcm"		 	   ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_menu_mcm_about"		,size= {512,50}		},
					{ id= "desc_mcm"    	 	,type= "desc"     ,text= "ui_mcm_desc_mcm"			 ,clr= {255,125,175,200}	},
					{ id= "desc_mcm2"    	 	,type= "desc"     ,text= "ui_mcm_desc_mcm2"			 ,clr= {255,125,175,200}	},
					--{ id="reset"	              ,type= "check"    ,val= 1	,def= false 	    , functor = {reload }  },
				},},
				{id = "mcm_kb",  gr={
					{id = "mcm_kb_main",  sh=true    ,gr={
						{ id= "slide_kb"		 	   ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_menu_mcm_kb"		,size= {512,50}		},
						{ id= "desc_kb"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_kb"			 ,clr= {255,125,175,200}	},
						{ id= "desc_kb2"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_kb2"			 ,clr= {255,125,175,200}	},
						{id = "dtaptime2", type = "track", val = 2, def = 200, min = 100, max = 750, step = 5},
						{id = "presstime", type = "track", val = 2, def = 1.5, min = 1.2, max = 2, step = 0.01, prec = 2},

					},},
					{id = "mcm_all_kb", sh=true    , gr={
						{ id= "slide_kb"		 	   ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_menu_mcm_all_kb"		,size= {512,50}		},
						{ id= "desc_kb"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_kb_all"			 ,clr= {255,125,175,200}	},
						{ id= "desc_kb2"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_kb_all2"			 ,clr= {255,125,175,200}	},
					},},
					{id = "mcm_kb_conflicts", sh=true    , gr={
						{ id= "slide_kb"		 	   ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_menu_mcm_kb_conflicts"		,size= {512,50}		},
						{ id= "desc_kb"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_kb_conflicts"			 ,clr= {255,125,175,200}	},
						{ id= "desc_kb2"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_kb_all2"			 ,clr= {255,125,175,200}	},
					},},
					
					
				},},
				{id = "mcm_log", sh=true    , gr={
					{ id= "slide_log"		 	   ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_menu_mcm_log"		,size= {512,50}		},
					{ id= "desc_log"    	 	,type= "desc"     ,text= "ui_mcm_mcm_desc_log"			 ,clr= {255,125,175,200}	},
					{id = "enable", type = "check", val = 1, def = true},
					{id = "numlogs", type = "track", val = 2, def = 2, min = 1, max = 10, step = 1},
					{id = "continuous", type = "check", val = 1, def = false},
					{id = "savefreq", type = "input", val = 2, def = 1000, min = 100, max = 1000000000000000},
					{id = "timestamp", type = "input", val = 2, def = 1000, min = 1, max = 1000000000000000},
					{id = "debug_logging", type = "line"},
					{id = "debug_logging2", type = "check", val = 1, def = false},
				},},

			},
		}
	}
	local kb_meta = {all = {}, conflict = {}}
	gathering = true
	gather_options()
	gathering = false	
	table.sort(options, function(a,b)
		if a.id == "mcm" then return true end
		if b.id == "mcm" then return false end
		return game.translate_string(a.text or opt_str_menu .. a.id) < game.translate_string(b.text or opt_str_menu .. b.id)
	end)

	init_opt_coder(kb_meta)

	table.sort(kb_meta.conflict, function(a,b) return display_key(get(a.kb_path)) < display_key(get(b.kb_path)) end)
	for i = 1, #kb_meta.all do
		options[1].gr[2].gr[2].gr[3+i] = kb_meta.all[i]
		options[1].gr[2].gr[3].gr[3+i] = kb_meta.conflict[i]
	end


	init_opt_coder()
	MCM_DEBUG = get("mcm/mcm_log/debug_logging2")
end


function init_opt_coder(kb_meta)
	local keybind_count = 0
	------------------------------------------------------------------------
	-- Coding options
	local function code_option(gr, id, num)
		local path
		for i=1,#gr do
			if allowed_type[gr[i].type] then
				path            = cc(id , gr[i].id)
				opt_index[path] = cc(num , i)
				opt_val[path]  = assert(gr[i].val, "val is manditory! option path:"..path)
				--printf("-[%s] | index: %s - type: %s", path, opt_index[path], opt_val[path])
				if gr[i].type == "key_bind" and kb_meta then
					keybind_count = keybind_count + 1
					update_conflict(path, get(path))
					local temp = dup_table(gr[i])
					temp.id = temp.id .. "_"..keybind_count
					temp.kb_path = path
					temp.curr = {get,path}
					temp.hint = temp.hint or id.."_"..gr[i].id
					temp.functor = {function(p,v) set(p,v) update_conflict(p,v) end, path  }
					if path:find("mcm/key_wrapper/") then
						temp.precondition = {function() return false end} --temp.precondition = {get, path:sub(1, -).."enable"}
					end
					kb_meta.all[#kb_meta.all+1] = dup_table(temp)
					temp.precondition = {get_conflict, path}
					kb_meta.conflict[#kb_meta.conflict+1] = dup_table(temp)
				end
			end
		end
	end
	
	local id_1, id_2, id_3
	-- Level 1
	for i=1,#options do
		id_1 = options[i].id
		if options[i].sh then
			code_option(options[i].gr, id_1, i)
		else
			-- Level 2
			for ii=1,#options[i].gr do
				id_2 = options[i].gr[ii].id
				if options[i].gr[ii].sh then
					code_option( (options[i].gr[ii].gr), (id_1 .._opt_.. id_2), (i .._opt_.. ii) )
				else
					-- Level 3
					for iii=1,#options[i].gr[ii].gr do
						id_3 = options[i].gr[ii].gr[iii].id
						if options[i].gr[ii].gr[iii].sh then
							code_option( (options[i].gr[ii].gr[iii].gr), (id_1 .._opt_.. id_2 .._opt_.. id_3), (i .._opt_.. ii .._opt_.. iii) )
						else
							----
						end
					end
			
				end
			end
		end
	end

end

function gather_options()
	-- this is modified from how axr_main loads all the other scripts thanks to Igi for the idea.
	local ignore = { 
		["ui_mcm.script"] = true,

	}
	
	local t = {}
	local size_t = 0 
	local f	= getFS()
	local flist = f:file_list_open_ex("$game_scripts$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly),"*mcm.script")
	local f_cnt = flist:Size()
	for	it=0, f_cnt-1 do
		local file = flist:GetAt(it)
		local file_name = file:NameShort()
		--printf("%s size=%s",file_name,file:Size())
		if (file:Size() > 0 and ignore[file_name] ~= true) then
			file_name = file_name:sub(0,file_name:len()-7)
			if (_G[file_name] and _G[file_name].on_mcm_load) then
				size_t = size_t + 1 
				t[size_t] = file_name -- load all scripts first
			end
		end
	end
	for i=1,#t do
		--printf("%s.on_game_start()",t[i])
		local temp = nil
		temp, name = _G[ t[i] ].on_mcm_load(options) -- passing options by referance added in AMCM 1.1 if you use this, nil check, emphasise updating MCM in your mod desc or something
		if temp and name then
			local collect = false
			for j=1, #options do
				if options[j].id == name then
					collect = j
				end
			end
			if not collect then
				collection = { id = name        , gr = {}}
				table.insert(collection.gr, temp)
				table.insert(options, collection)
			else
				table.insert(options[collect].gr, temp)
			end
		elseif temp then
			table.insert(options, temp)
		end
	end

end


------------------------------------------------------------
-- Functors
------------------------------------------------------------

-- Special
local function empty_functor()
	print_dbg("Empty functor called!")
end


	

function reload()
	set("mcm/reset", false)
	init_opt_base()
end

-- Preconditions
function level_present()
	return level.present()
end
function debug_only()
	return DEV_DEBUG
end
function for_renderer(...)
	local rend = {...}
	local curr_rend = get_console_cmd(0, "renderer")
	local result = false
	for i=1,#rend do
		result = result or curr_rend == rend[i]
	end
	return result
end

--Key bind stuff
local function set_conflict(ck)
	local conflict = nil
	for k, v in pairs(paths_by_key[ck]) do
		if k ~= "conflict" then
			conflict = conflict or conflict == false or false --if it is true it stays true, if false it becomes true if nil it becomes false. 0 entries = nil, 1 entry = false, more than 1 entry = true
		end
	end
	paths_by_key[ck].conflict = conflict
	print_dbg("set conflict ck:%s con:%s",ck,conflict)
end

function update_conflict(path, key)
	key = tonumber(key)
	local old_key = key_by_path[path]
		print_dbg("path:%s, key:%s, old:%s", path, key, old_key)

	if key == old_key then return end
	key_by_path[path] = key
	if old_key then
		paths_by_key[old_key][path] = nil
		if paths_by_key[old_key].conflict then -- if this key used to have a conflict see if it can be clered
			set_conflict(old_key)
		end
	end
	if not paths_by_key[key] then
		paths_by_key[key] = {}
	end
	paths_by_key[key][path] = true
	if not paths_by_key[key].conflict then --if this key didn't have a conflict see if it does now
		set_conflict(key)
	end
	
end

function get_conflict(path, key)
	local k = key or key_by_path[path]
	print_dbg("path:%s, key:%s, k:%s", path, type(key), type(k))
	assert(k, "No key found for path")
	k = tonumber(k)
	if k == -1 then return false end
	if  dik_to_bind(k) ~= dik_to_bind(1000) then --conflicts with engine keybind
		print_dbg("bind:%s", dik_to_bind(k))
		return true
	end
	
	if key_by_path[path] == k then
		print_dbg("==")
		return paths_by_key[k] and paths_by_key[k].conflict
	end
	print_dbg("=/= kbp:%s %s, k:%s %s",type(key_by_path[path]), key_by_path[path], k, type(k))
	return paths_by_key[k] and (paths_by_key[k].conflict or paths_by_key[k].conflict == false) --limited support for pending changes conflicts.
end

function display_key(key)
	local loc = ini_loc:r_value("string_table","language")
	loc = ini_mcm_key:section_exist(loc) and loc or "eng"
	return ini_mcm_key:r_value(loc, key,0,"<!!!>")
end

dispaly_key = display_key --old typo aliased to prevent breaking mods. 

kb_mod_radio = "radio_h"
kb_mod_list = "list"

local tap = {}
function double_tap(id, key, multi_tap)
	local tg = time_continual()	
	tap[key] = tap[key] or {}
	if not tap[key][id] then --first press, set timer return false
		tap[key][id] = tg
		return false
	end
	local dtaptime = get("mcm/mcm_kb/mcm_kb_main/dtaptime2")
	
	if (tg - tap[key][id] <= dtaptime) then --if inside the dtap window
		tap[key][id] = multi_tap and tg -- if multi_tap set timer for next tap, else clear it.
		return true
	end
	
	tap[key][id] = tg -- if we get here we are past the window for a double tap so this is a first press
	return false
end


local hold = {}
function key_hold(id,key, cycle)
	local dtaptime = get("mcm/mcm_kb/mcm_kb_main/dtaptime2")
	local hold_time = get("mcm/mcm_kb/mcm_kb_main/presstime") * dtaptime
	cycle = cycle or 10000
	local tg = time_continual()
	hold[key] = hold[key] or {}
	if (not hold[key][id]) or (tg - hold[key][id] > hold_time*1.5) then
		hold[key][id] = tg
	elseif (tg - hold[key][id] > hold_time ) then			
		hold[key][id] = tg + cycle * 1000
		return true
	end
	return false
end

local function execute_if_not_held(id,key, functor,...)
	if not (hold[key] and hold[key]["mcm_single_"..id]) then
		exec(functor,...)
	end
	return true
end

function simple_press(id, key, functor,...)
	
	dtap = double_tap("mcm_single_"..id, key, true)
	local tg = time_continual()
	hold[key] = hold[key] or {}
	hold[key]["mcm_single_"..id] = tg
	local dtaptime = get("mcm/mcm_kb/mcm_kb_main/dtaptime2")
	if dtap then
		RemoveTimeEvent("mcm_single_press",id.."_mcm_single_"..key)
	else
		CreateTimeEvent("mcm_single_press",id.."_mcm_single_"..key,(dtaptime*1.1)/1000,execute_if_not_held,id, key, functor,...)
	end


end




MOD_NONE = true
MOD_CTRL = false
MOD_SHIFT = false
MOD_ALT = false

function get_mod_key(val)
	if val == 1 then
		return MOD_SHIFT
	elseif val == 2 then
		return MOD_CTRL
	elseif val == 3 then
		return MOD_ALT
	end
	return MOD_NONE
end

function on_key_release(key)
	hold[key] = nil
	if key == DIK_keys.DIK_RCONTROL or key == DIK_keys.DIK_LCONTROL then
		MOD_CTRL = false
	elseif key == DIK_keys.DIK_RSHIFT or key == DIK_keys.DIK_LSHIFT then
		MOD_SHIFT = false
	elseif key == DIK_keys.DIK_RMENU or key == DIK_keys.DIK_LMENU then
		MOD_ALT = false
	elseif key == DIK_keys.DIK_ESCAPE then
		MOD_CTRL = false
		MOD_SHIFT = false
		MOD_ALT = false
	end
	MOD_NONE = not(MOD_CTRL or MOD_SHIFT or MOD_ALT)
	
end
function on_key_press(key)
	if key == DIK_keys.DIK_RCONTROL or key == DIK_keys.DIK_LCONTROL then
		MOD_CTRL = true
	elseif key == DIK_keys.DIK_RSHIFT or key == DIK_keys.DIK_LSHIFT then
		MOD_SHIFT = true
	elseif key == DIK_keys.DIK_RMENU or key == DIK_keys.DIK_LMENU then
		MOD_ALT = true

	end
	MOD_NONE = not(MOD_CTRL or MOD_SHIFT or MOD_ALT)
	
end



-- Utilities
function is_int(num)
	return (m_floor(num) == num)
end
function exec(func,...)
	if (not func) then
		return false
	end
	return func(...)
end
function str_opt_explode(id, by_num)
	local nums = by_num and opt_index[id] or id
	local t = nums and str_explode(nums, _opt_) or {}
	if by_num then
		for i=1,#t do
			t[i] = tonumber(t[i])
		end
	end
	return t
end
function get_opt_table(id)
	local t = str_opt_explode(id, true)
	if #t == 0 then
		return {}
	end
	
	if #t == 1 then
		return options[t[1]]
	elseif #t == 2 then
		return options[t[1]].gr[t[2]]
	elseif #t == 3 then
		return options[t[1]].gr[t[2]].gr[t[3]]
	elseif #t == 4 then
		return options[t[1]].gr[t[2]].gr[t[3]].gr[t[4]]
	end
end
function check_opt_table(id)
	local t = str_opt_explode(id, true)
	return #t > 0
end
function CTimeTo_mSec(ct)
	local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
	Y, M, D, h, m, s, ms = ct:get(Y, M, D, h, m, s, ms)
	return ((D*24*60*60 + h*60*60 + m*60 + s)*1000 + ms)
end

local function create_session_id()
	if not  session_id then
		session_id = axr_main.config:r_value(opt_section, "session_id", 2, 0)
		
		if not level.present() then -- if there is a level present then this is an inprogress session, use ID in file. if not compare the stored session start to the curent time and time continual to identify new session
			local session_start = axr_main.config:r_value(opt_section, "session_start", 2, 0)
			local now = os.time() * 1000
			if (now - session_start) > (time_continual() + 10000) then --ten seconds of slop
				session_id = session_id + 1
				axr_main.config:w_value(opt_section, "session_id", session_id)
				axr_main.config:w_value(opt_section, "session_start", now)
				axr_main.config:save()
				printf("MCM Session ID:%s", session_id) 
			end
		end
	end
end

function get_session_id()
	create_session_id()
	return session_id
end

function is_gathering()
	return gathering
end
--------------------
function get(id)
	assert(not gathering, "ui_mcm.get() cannot be called during script load or on_mcm_load()!")

	if (#options == 0) then
		init_opt_base()
	end
	
	if not opt_val[id] then
		printe("MCM given bad path:%s",id)
		return
	end

	
	
	
	local value = axr_main.config:r_value(opt_section, id, opt_val[id])
	if (value ~= nil) then
		--print_dbg("/Got axr_option [%s] = %s", id, value)
		return value
	end
	
	-- Write in axr_main if it doesn't exist
	local v = get_opt_table(id)
	if v.cmd then
		if v.val == 0 then
			value = get_console_cmd(0, v.cmd)
		elseif v.val == 1 then
			value = get_console_cmd(1, v.cmd)
		elseif v.val == 2 then
			value = get_console_cmd(0, v.cmd)	--get_console_cmd(2, v.cmd)
			value = tonumber(value)
			if v.min and v.max then
				value = clamp(value, v.min, v.max)
			end
			value = round_idp(value, v.prec or precision)
		end
	elseif (type(v.def) == "table") then
		value = exec(unpack(v.def))
		axr_main.config:w_value(opt_section, id, value)
		axr_main.config:save()
	else
		value = v.def
		axr_main.config:w_value(opt_section, id, value)
		axr_main.config:save()
	end
	
	--print_dbg("/Got option [%s] = %s", id, value)
	if (value == nil) then
		printe("!Found nil option value [%s]", id)
	end
	
	return value
end

function set(id, value)
	axr_main.config:w_value(opt_section, id, value)
	axr_main.config:save()
end
--------------------


--===========================================================
--//////////////////////// OPTIONS //////////////////////////
--===========================================================
UIMCM = {} --old monkey patch black hole

class "UI_MCM" (CUIScriptWnd)

function UI_MCM:__init() super()
	self.last_tree = {}
	self.last_path = nil
	self.last_curr_tree = nil
	
	self._Cap = {}
	self._Check = {}
	self._List = {}
	self._Input = {}
	self._Track = {}
	self._Radio = {}
	self._Hint = {}


	-- Prepare the options table
	if (#options == 0) then
		init_opt_base()
	end
	printf("MCM init")
	self:InitControls()
    self:InitCallBacks()
	
	self:Reset()
end

function UI_MCM:__finalize()
end

function UI_MCM:InitControls()
	self:SetWndRect				(Frect():set(0,0,1024,768))
	self:Enable					(true)

	self.xml = CScriptXmlInit()
	local xml = self.xml
	xml:ParseFile				("ui_mcm.xml")
	
	self.background 			= xml:InitStatic("background", self)
	self.dialog					= xml:InitStatic("main", self)
	
	xml:InitStatic("main:frame", self.dialog)
	
	-- Buttons
	self.btn_accept = xml:Init3tButton("main:btn_accept", self.dialog)
	self:Register(self.btn_accept, "btn_accept")
	
	self.btn_reset = xml:Init3tButton("main:btn_reset", self.dialog)
	self:Register(self.btn_reset, "btn_reset")
	
	self.btn_default = xml:Init3tButton("main:btn_default", self.dialog)
	self:Register(self.btn_default, "btn_default")
	
	self.btn_cancel = xml:Init3tButton("main:btn_cancel", self.dialog)
	self:Register(self.btn_cancel, "btn_cancel")
	
	-- Pending text
	--xml:InitFrame("main:notify_frame", self.dialog)
	self.pending = xml:InitTextWnd("main:notify", self.dialog)
	
	-- Options lists
	self.tree = {}
	self.bl = {}
	
	-- Options showcase
	self.scroll_opt = xml:InitScrollView("main:scroll", self.dialog)
	
	-- Presets
	self.preset_cap = xml:InitStatic("main:cap_preset", self.dialog)
	self.preset = xml:InitComboBox("main:preset",self.dialog)
	self:Register(self.preset, "preset")
	self.preset:Show(false)
	self.preset_cap:Show(false)
	
	-- Message box
	self.message_box			= CUIMessageBoxEx()
	self:Register				(self.message_box, "mb")
	
	-- Hint Window
	self.hint_wnd = utils_ui.UIHint(self)
	
	--MCM Key Bind
	self.k_binder = nil
	self.k_timer = 0
	self.key_input = nil
end

function UI_MCM:InitCallBacks()
	self:AddCallback("btn_accept", ui_events.BUTTON_CLICKED, self.OnButton_Accept, self)
	self:AddCallback("btn_reset", ui_events.BUTTON_CLICKED, self.OnButton_Reset, self)
	self:AddCallback("btn_default", ui_events.BUTTON_CLICKED, self.OnButton_Default, self)
	self:AddCallback("btn_cancel", ui_events.BUTTON_CLICKED, self.OnButton_Cancel, self)
	
	self:AddCallback("preset", ui_events.LIST_ITEM_SELECT, self.Callback_Preset, self)
	
	self:AddCallback("mb", ui_events.MESSAGE_BOX_YES_CLICKED, self.On_Discard, self)
	--self:AddCallback("mb", ui_events.MESSAGE_BOX_NO_CLICKED, self.On_Discard,self)
end

function UI_MCM:Update()
	CUIScriptWnd.Update(self)

	-- Show hint on hover
	for id,ctrl in pairs(self._Cap) do
		if ctrl:IsCursorOverWindow() then
			local str = opt_str .. (self._Hint[id] or id) .. "_desc"
			local str_t = game.translate_string(str)
			if (str ~= str_t) then
				self.hint_wnd:Update(str_t)
			end
			return
		end
	end
	
	self.hint_wnd:Update()
	
	-- Hack to simulate tracing method for TrackBar value changes. TODO: add callback support for CUITrackBar in engine, this is just silly
	for id,e in pairs(self._Track) do
		if e.ctrl:IsCursorOverWindow() then
			local v = self:GetOption(id)
			local value = round_idp(e.ctrl:GetFValue(), v.prec or precision)
			if (value ~= e.value) then
				e.value = value
				self:Callback_Track(e.txt, e.path, e.opt, v, value)
				return
			end
		end
	end
end

function UI_MCM:Reset()
	-- Clear all trees
	for i=1,3 do
		if self.tree[i] then
			if type(self.tree[i]) == table then
				for j=1,#self.tree[i] do
					self.tree[i][j]:Clear()
				end
			else
				self.tree[i]:Clear()
			end
		end
	end
	self.k_binder = nil
	self.k_timer = 0
	self.key_input = nil

	self:Register_Tree(1, "", options, 1)
end

function UI_MCM:Reset_opt(curr_tree, path, flags)
	flags = flags or {}
	local xml = self.xml
	self.scroll_opt:Clear()
	
	-- If options tree has a precondition that must be met, don't show it if it returns false
	if curr_tree.precondition and (not exec(unpack(curr_tree.precondition))) then
		if curr_tree.output then
			local _txt = xml:InitTextWnd("elements:block", nil)
			_txt:SetText( game.translate_string(curr_tree.output) )
			
			self.scroll_opt:AddWindow(_txt, true)
			_txt:SetAutoDelete(false)
		end
	else
		
		
		self.k_binder = nil
		self.k_timer = 0
		self.key_input = nil

		
		-- Presets
		self:Register_Preset(curr_tree)
		
		if curr_tree.apply_to_all and curr_tree.id_gr then
			flags.apply_to_all = true
			flags.group = curr_tree.id_gr
		else
			flags.apply_to_all = nil
		end
		
		empty_table(self._Cap)
		empty_table(self._Check)
		empty_table(self._List)
		empty_table(self._Input)
		empty_table(self._Track)
		empty_table(self._Radio)
		empty_table(self._Hint)
		
		for i=1,#curr_tree.gr do
			-- Check preconditions
			local to_hide = curr_tree.gr[i].precondition and (not exec(unpack(curr_tree.gr[i].precondition)))
			for j=1,10 do -- support for 10 preconditions
				if (not curr_tree.gr[i]["precondition_" .. j]) then
					break
				elseif (not exec(unpack(curr_tree.gr[i]["precondition_" .. j]))) then
					to_hide = true
					break
				end
			end
			
			if (not to_hide) then
				local opt = curr_tree.gr[i].id
				local v = curr_tree.gr[i]
				
				local _st = xml:InitStatic("main:st", nil)
				local _h = 0
----------- Support
				if (v.type == "line") then
					_h = self:Register_Line(xml, _st, v)
		
				elseif (v.type == "image") then
					_h = self:Register_Image(xml, _st, v)
					
				elseif (v.type == "slide") then
					_h = self:Register_Slide(xml, _st, v)
				
				elseif (v.type == "title") then
					_h = self:Register_Title(xml, _st, v)
				
				elseif (v.type == "desc") then
					_h = self:Register_Desc(xml, _st, v)
			
----------- Option
				elseif (v.type == "check") then
					_h = self:Register_Check(xml, _st, path, opt, v, flags)
					
				elseif (v.type == "button") then
					_h = self:Register_Button(xml, _st, path, opt, v, flags)
					
				elseif (v.type == "list") then
					_h = self:Register_List(xml, _st, path, opt, v, flags)
				
				elseif (v.type == "input") then
					_h = self:Register_Input(xml, _st, path, opt, v, flags)
				
				elseif (v.type == "track") then
					_h = self:Register_Track(xml, _st, path, opt, v, flags)
					
				elseif (v.type == "radio_h") then
					_h = self:Register_Radio(xml, _st, path, opt, v, true, flags)
					
				elseif (v.type == "radio_v") then
					_h = self:Register_Radio(xml, _st, path, opt, v, false, flags)

				elseif (v.type == "key_bind") then
					_h = self:Register_Key_Bind(xml, _st, path, opt, v,  flags)
					
				end
				
				_st:SetWndSize(vector2():set(_st:GetWidth(), _h + 10))
				self.scroll_opt:AddWindow(_st, true)
				_st:SetAutoDelete(true)
			end
		end
		if self.Save_AXR then
			self.Save_AXR = false
			axr_main.config:save()
		end
	end
end

function UI_MCM:Reset_last_opt()
	Register_UI("UI_MCM")
	
	if self.last_curr_tree and self.last_path then
		if self.last_path == "mcm/mcm_kb/mcm_conflicts" then
			self.last_curr_tree = options[1].gr[2].gr[3]
		end
		self:Reset_opt(self.last_curr_tree, self.last_path)
		self:UpdatePending()
	end
end

------------------------------------------------------------
-- Elements
------------------------------------------------------------

function UI_MCM:Init_Wrapper_Box(xml, anchor, w, h, posx, posy)
	if not (xml and anchor) then return end
	wrapbox	= xml:InitStatic("elements:image", anchor)
	if not wrapbox then return end
	w 		= w or anchor:GetWidth()
	h 		= h or anchor:GetHeight()
	posx 	= posx and (type(posx) == "number") and posx or 0
	posy 	= posy and (type(posy) == "number") and posy or 0
	wrapbox:SetWndSize(vector2():set(w,h))
	pos		= wrapbox:GetWndPos()
	wrapbox:SetWndPos(vector2():set(pos.x + posx, pos.y + posy))
	return wrapbox
end

function UI_MCM:Register_Cap(xml, handler, id, hint)
	id = s_gsub(id, _opt_, "_")
	self._Cap[id] = xml:InitStatic("elements:cap",handler)
	self._Cap[id]:TextControl():SetText( game.translate_string(opt_str .. (hint or id)) )
	self._Hint[id] = hint
	return self._Cap[id]:GetHeight()
end

function UI_MCM:Register_Line(xml, handler, v)
	-- v parameter added to support ui_hook_functor
	local line = xml:InitStatic("elements:line",handler)
	if enable_ui_functors and v and v.ui_hook_functor then
		-- Test for existence first since older versions of MCM won't be passing v to this function
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,line:GetWidth(),line:GetHeight() + 10,-10)
		local handlers = {
			line 		= line, 	-- handler for the line element
		}
		local flags = {
			etype 		= "line",
		}
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end
	
	return (line:GetHeight() + 10)
end

function UI_MCM:Register_Image(xml, handler, v)
	local pic = xml:InitStatic("elements:image",handler)
	if v.link then
		if (v.pos) then
			local pos = pic:GetWndPos()
			pic:SetWndPos(vector2():set( pos.x + v.pos[1] , pos.y + v.pos[2] ))
		end
		if (v.size) then
			pic:SetWndSize(vector2():set( v.size[1] , v.size[2] ))
		end
		pic:InitTexture(v.link)
		pic:SetStretchTexture(v.stretch and true or false)
	end

	if enable_ui_functors and v.ui_hook_functor then
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,pic:GetWidth(),pic:GetHeight(),-10)
		local handlers = {
			pic 		= pic, 	-- Handler for the image element
		}
		local flags = {
			etype 		= "image",
		}
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end
	
	return pic:GetHeight()
end

function UI_MCM:Register_Slide(xml, handler, v)
	local frame = xml:InitStatic("elements:slide", handler)
	local _pos = frame:GetWndPos()
	frame:SetWndPos(vector2():set( _pos.x , _pos.y + (v.spacing or 20) ))
	
	local pic = xml:InitStatic("elements:slide:pic", frame)
	if v.link then
		pic:InitTexture(v.link)
		pic:SetStretchTexture(true)
		if (v.pos) then
			local pos = pic:GetWndPos()
			pic:SetWndPos(vector2():set( pos.x + v.pos[1] , pos.y + v.pos[2] ))
		end
		if (v.size) then
			pic:SetWndSize(vector2():set( v.size[1] * width_factor , v.size[2] ))
		end
		pic:InitTexture(v.link)
	end
	
	local txt = xml:InitTextWnd("elements:slide:txt", frame)
	if v.text then
		txt:SetText( game.translate_string(v.text) )
	end

	if not v.borderless then
		xml:InitStatic("elements:slide:line_1", frame)
		xml:InitStatic("elements:slide:line_2", frame)
	end

	if enable_ui_functors and v.ui_hook_functor then
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,pic:GetWidth(),pic:GetHeight() + 20,-10)
		local handlers = {
			pic 		= pic, 	-- handler for the image element
			txt 		= txt, 	-- handler for the text element
		}
		local flags = {
			etype 		= "slide",
		}
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end

	return (pic:GetHeight() + 20)
end

function UI_MCM:Register_Title(xml, handler, v)
	local title = xml:InitTextWnd("elements:title_" .. (v.align or "l"), handler)
	title:SetText( game.translate_string(v.text) )
	title:AdjustHeightToText()
	title:SetWndSize(vector2():set(title:GetWidth(), title:GetHeight() + 20))
	if v.clr and v.clr[4] then
		title:SetTextColor( GetARGB(v.clr[1], v.clr[2], v.clr[3], v.clr[4]) )
	end
	if enable_ui_functors and v.ui_hook_functor then
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,title:GetWidth(),title:GetHeight(),-10)
		local handlers = {
			title 		= title, -- Handler for the title element
		}
		local flags = {
			etype 		= "title",
		}
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end

	return title:GetHeight()
end

function UI_MCM:Register_Desc(xml, handler, v)
	local desc = xml:InitTextWnd("elements:desc", handler)
	desc:SetText( game.translate_string(v.text) )
	desc:AdjustHeightToText()
	desc:SetWndSize(vector2():set(desc:GetWidth(), desc:GetHeight() + 20))
	if v.clr and v.clr[4] then
		desc:SetTextColor( GetARGB(v.clr[1], v.clr[2], v.clr[3], v.clr[4]) )
	end

	if enable_ui_functors and v.ui_hook_functor then
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,desc:GetWidth(),desc:GetHeight(),-10)
		local handlers = {
			desc 		= desc, -- Handler for the text description element
		}
		local flags = {
			etype 		= "desc",
		}
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end

	return desc:GetHeight()
end

function UI_MCM:Register_Check(xml, handler, path, opt, v, flags)
	local id = cc(path , opt)
	
	-- Caption
	local h = self:Register_Cap(xml, handler,  id, v.hint)
	
	-- Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end

	-- Create control
	local ctrl = xml:InitCheck("elements:check",handler)
	if (ctrl:GetHeight() > h) then
		h = ctrl:GetHeight()
	end
	
	-- Get values
	local value = self:GetValue(path, opt, v, flags)
	ctrl:SetCheck(value)
	
	-- Register
	local id_ctrl = self:Stacker(path, opt, v)
	
	self:Register(ctrl, id_ctrl)
	local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
		self:Callback_Check(ctrl, path, opt, v)
	end
	self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)
	if enable_ui_functors and v.ui_hook_functor then
		local id_cap 	= s_gsub(id, _opt_, "_")
		local cap 		= self._Cap[id_cap]
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),h,-10)
		local handlers = {
			ctrl 		= ctrl, 	-- handler for the checkbox control element
			cap 		= cap, 		-- handler for text caption element
		}
		flags.etype 	= "check"
		flags.path 		= path 		-- MCM menu path
		flags.opt		= opt 		-- MCM option ID
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end
	
	return h
end
function UI_MCM:Callback_Check(ctrl, path, opt, v)
	local value = ctrl:GetCheck()
	self:CacheValue(path, opt, value, v)
end

function UI_MCM:Register_Key_Bind(xml, handler, path, opt, v, flags)
	local id = cc(path , opt)
	
	self.k_binder = nil
	self.k_timer = 0
	self.key_input = nil

	
	--[[ Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end
	--]]
	xml:InitFrame("elements:frame_key_button", handler)
	local txt = xml:InitStatic("elements:txt_key_button", handler)
	
	-- Create control
	local ctrl = xml:Init3tButton("elements:btn_key_button", handler)
	local h = ctrl:GetHeight()
	
	-- Caption
	local h = self:Register_Cap(xml, handler,  id, v.hint)

	-- Get values
	local value = self:GetValue(path, opt, v, flags)

	txt:TextControl():SetText(display_key(value))

	if get_conflict(v.kb_path or id, value) then
		txt:TextControl():SetTextColor(clr_r)
	else
		txt:TextControl():SetTextColor(clr_g1)
	end
	-- Register
	local id_ctrl = self:Stacker(path, opt, v)

	self:Register(ctrl, id_ctrl)
	local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
		self:Callback_Key_Bind(ctrl, path, opt, v, txt)
	end

	self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)

	return h
end

function UI_MCM:Callback_Key_Bind(ctrl, path, opt, v, txt)
	self.key_input = txt:TextControl()
	self.key_input:SetText( "<?>")
	txt:TextControl():SetTextColor(clr_w)
	if self.k_binder then
		self.k_binder() -- clear other bind inputs
	end
	self.k_timer = time_continual()
	self.k_binder = function(key)
		self:Key_Binder(ctrl, path, opt, v, key, txt)
	end
end

function UI_MCM:Key_Binder(ctrl, path, opt, v, key, txt)
	
	self.key_input = nil
	self.k_binder = nil
	local value
	if key then
		self:CacheValue(path, opt, key, v)
		value = key
	else
		value = self:GetValue(path, opt, v, flags)
	end
	txt:TextControl():SetText(display_key(value))
	if get_conflict(v.kb_path or  cc(path , opt), value) then
		txt:TextControl():SetTextColor(clr_r)
	else
		txt:TextControl():SetTextColor(clr_g1)
	end

end

function UI_MCM:Register_Button(xml, handler, path, opt, v, flags)
	local id = cc(path , opt)
	
	
	
	--[[ Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end
	--]]
	
	xml:InitFrame("elements:frame_button", handler)
	
	-- Create control
	local ctrl = xml:Init3tButton("elements:btn_button", handler)
	local h = ctrl:GetHeight()
	
	-- Caption
	local id_cap = s_gsub(id, _opt_, "_")
	self._Cap[id_cap] = xml:InitStatic("elements:cap_button",handler)
	self._Cap[id_cap]:TextControl():SetText( game.translate_string(opt_str .. (v.hint or id_cap)) )
	if (self._Cap[id_cap]:GetHeight() > h) then
		h = self._Cap[id_cap]:GetHeight()
	end
	
	-- Register
	local id_ctrl = self:Stacker(path, opt, v)
	
	self:Register(ctrl, id_ctrl)
	local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
		self:Callback_Button(ctrl, path, opt, v)
	end
	self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)

	return h
end



function UI_MCM:Callback_Button(ctrl, path, opt, v)
	if v.functor_ui then
		local id = cc(path , opt)
		print_dbg("- Executing functor_ui of [%s]",id)
		exec(unpack(v.functor_ui),self)
	end
	if v.functor then
		local id = cc(path , opt)
		print_dbg("- Executing functor of [%s]",id)
		exec(unpack(v.functor))
	end
end

function UI_MCM:Register_List(xml, handler, path, opt, v, flags)
	local id = cc(path , opt)
	
	-- Caption
	local h = self:Register_Cap(xml, handler,  id, v.hint)
	
	-- Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end
	
	-- Create control
	local ctrl = xml:InitComboBox("elements:list",handler)
	if (ctrl:GetHeight() > h) then
		--h = ctrl:GetHeight()
	end
	
	-- Get values
	local idx
	local value = self:GetValue(path, opt, v, flags)
	local content = self:GetContent(path, opt, v)
	
	-- Setup
	for i=1,#content do 
		local str_2 = content[i][2] or tostring(content[i][1])
		local str = v.no_str and str_2 or game.translate_string(opt_str_lst .. str_2)
		ctrl:AddItem( game.translate_string(str), i)
		
		if content[i][1] == value then
			idx = i
		end
	end
	idx = idx or 1
	--printf(path.." ".. idx)
	local str_2 = content[idx][2] or tostring(content[idx][1])
	local str = v.no_str and str_2 or game.translate_string(opt_str_lst .. str_2)
	ctrl:enable_id( idx )
	ctrl:SetText( game.translate_string(str) )
	
	-- Register
	local id_ctrl = self:Stacker(path, opt, v)
	
	self:Register(ctrl, id_ctrl)
	local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
		self:Callback_List(ctrl, path, opt, v)
	end
	self:AddCallback(id_ctrl, ui_events.LIST_ITEM_SELECT, _wrapper, self)

	if enable_ui_functors and v.ui_hook_functor then
		local id_cap 	= s_gsub(id, _opt_, "_")
		local cap 		= self._Cap[id_cap]
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),ctrl:GetHeight(),-10)
		local handlers = {
			ctrl 		= ctrl, 	-- handler for the list control element
			cap 		= cap, 		-- handler for text caption element
		}
		flags.etype 	= "list"
		flags.path 		= path 		-- MCM menu path
		flags.opt		= opt 		-- MCM option ID
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end
	
	return h
end
function UI_MCM:Callback_List(ctrl, path, opt, v)
	local i = ctrl:CurrentID()
	local content = self:GetContent(path, opt, v)
	self:CacheValue(path, opt, content[i][1], v)
end

function UI_MCM:Register_Input(xml, handler, path, opt, v, flags)
	local id = cc(path , opt)
	
	-- Caption
	local h = self:Register_Cap(xml, handler,  id, v.hint)
	
	-- Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end
	
	-- Create control
	local ctrl = xml:InitEditBox("elements:input",handler)
	if (ctrl:GetHeight() > h) then
		h = ctrl:GetHeight()
	end
	
	-- Get values
	local value = self:GetValue(path, opt, v, flags)
	ctrl:SetText(value)
	
	-- Register
	local id_ctrl = self:Stacker(path, opt, v)
	
	self:Register(ctrl, id_ctrl)
	local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
		self:Callback_Input(ctrl, path, opt, v)
	end
	self:AddCallback(id_ctrl, ui_events.EDIT_TEXT_COMMIT, _wrapper, self)

	if enable_ui_functors and v.ui_hook_functor then
		local id_cap 	= s_gsub(id, _opt_, "_")
		local cap 		= self._Cap[id_cap]
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),h,-10)
		local handlers = {
			ctrl 		= ctrl, 	-- handler for the input control element
			cap 		= cap, 		-- handler for text caption element
		}
		flags.etype		= "input"
		flags.path 		= path 		-- MCM menu path
		flags.opt		= opt 		-- MCM option ID
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end

	return h
end
function UI_MCM:Callback_Input(ctrl, path, opt, v)
	local value = ctrl:GetText()
	if not (value and value ~= "") then
		ctrl:SetText( self:GetCurrentValue(path, opt, v) or self:GetDefaultValue(path, opt, v) )
		return
	end
	
	if (v.val == 2) then
		value = tonumber(value)
		if (not value) then
			ctrl:SetText( self:GetCurrentValue(path, opt, v) or self:GetDefaultValue(path, opt, v) )
			return
		end
		value = clamp(value, v.min, v.max)
	end
	
	self:CacheValue(path, opt, value, v)
	ctrl:SetText(value)
end


function UI_MCM:Register_Track(xml, handler, path, opt, v, flags)
	local id = cc(path , opt)
	
	-- Caption
	local h = self:Register_Cap(xml, handler,  id, v.hint)
	
	-- Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end
	
	-- Create control
	self._Track[id] = {}
	self._Track[id].ctrl = xml:InitTrackBar("elements:track",handler)
	self._Track[id].path = path
	self._Track[id].opt = opt
	if (self._Track[id].ctrl:GetHeight() > h) then
		h = self._Track[id].ctrl:GetHeight()
	end
	
	self._Track[id].txt = xml:InitTextWnd("elements:track_value",handler)
	
	-- Get values
	local value = self:GetValue(path, opt, v, flags)
	value = clamp(value, v.min, v.max)
	value = round_idp(value, v.prec or precision)
	
	local int = false --is_int(value) and is_int(v.step) and is_int(v.min) and is_int(v.max)
	self._Track[id].value = value -- temp
	self._Track[id].ctrl:SetInvert(v.invert and true or false)
	self._Track[id].ctrl:SetStep(v.step)
	if int then
		self._Track[id].ctrl:SetOptIBounds(v.min, v.max)
		self._Track[id].ctrl:SetIValue(value)
	else
		self._Track[id].ctrl:SetOptFBounds(v.min, v.max)
		self._Track[id].ctrl:SetFValue(value)
	end
	if (not v.no_str) then
		self._Track[id].txt:SetText(value)
	end

	if enable_ui_functors and v.ui_hook_functor then
		local id_cap 	= s_gsub(id, _opt_, "_")
		local cap 		= self._Cap[id_cap]
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,10,10,-10)
		local ctrl		= self._Track[id].ctrl
		local txt 		= self._Track[id].txt
		local handlers = {
			ctrl		= ctrl, 	-- handler for input control
			txt 		= txt, 		-- handler for current value display text
			cap 		= cap, 		-- handler for text caption element
		}
		flags.etype		= "track"
		flags.path 		= path  	-- MCM menu path
		flags.opt		= opt 		-- MCM option ID
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
	end

	return h
end
function UI_MCM:Callback_Track(ctrl, path, opt, v, value)
	if (not v.no_str) then
		ctrl:SetText(value)
	end
	self:CacheValue(path, opt, value, v)
end

function UI_MCM:Register_Radio(xml, handler, path, opt, v, typ, flags)
	local id = cc(path , opt)

	-- Caption
	local h = self:Register_Cap(xml, handler,  id, v.hint)
	
	-- Apply to all button
	if flags.apply_to_all and flags.group then
		self:Register_BtnAll(xml, handler, path, opt, v, flags)
	end
	
	-- Determine type
	local str = typ and "horz" or "vert"
	local content = self:GetContent(path, opt, v)
	local num = #content
	if num > 8 and (not v.force_horz) then
		typ = false
		str = "vert"
	end
	
	-- Create control
	local frame = xml:InitStatic("elements:radio_" .. str, handler)
	local ctrl = {}
	local txttbl = {}
	local txt
	local offset = typ and m_floor(frame:GetWidth()/num) or 30
	local h_factor = typ and 1 or 0
	local v_factor = typ and 0 or 1
	local h1, h2 = 0, 0
	--printf("offset: %s - h_factor: %s - v_factor: %s - num: %s", offset, h_factor, v_factor, num)
	for i=1,num do 
	
		-- Buttons
		ctrl[i] = xml:InitCheck("elements:radio_" .. str .. ":btn", frame)
		local pos = ctrl[i]:GetWndPos()
		h1 = (h1 * v_factor) + ctrl[i]:GetHeight()
		ctrl[i]:SetWndPos(vector2():set( pos.x + ((i-1) * offset * h_factor) , pos.y + ((i-1) * offset * v_factor) ))
		
		-- Text
		txt = xml:InitTextWnd("elements:radio_" .. str .. ":txt", frame)
		local pos2 = txt:GetWndPos()
		h2 = h_factor * txt:GetHeight()
		txt:SetWndPos(vector2():set( pos2.x + ((i-1) * offset * h_factor) , pos2.y - (v_factor * 30) + ((i-1) * offset * v_factor) ))
		local str_2 = content[i][2] or tostring(content[i][1])
		local str = v.no_str and game.translate_string(str_2) or game.translate_string(opt_str_lst .. str_2)
		txt:SetText( str )
		txttbl[i] = txt
		if (h1 + h2 > h) then
			h = h1 + h2
		end
	end
	
	-- Get values
	local value = self:GetValue(path, opt, v, flags)
	
	local id_ctrl = self:Stacker(path, opt, v)
	for i=1,num do 
		if (content[i][1] == value) then
			ctrl[i]:SetCheck(true)
		else
			ctrl[i]:SetCheck(false)
		end
		
		-- Register
		self:Register(ctrl[i], id_ctrl .. i)
		local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
			self:Callback_Radio(ctrl, path, opt, v, i)
		end
		self:AddCallback(id_ctrl .. i, ui_events.BUTTON_CLICKED, _wrapper, self)
	end


	if enable_ui_functors and v.ui_hook_functor then
		local id_cap 	= s_gsub(id, _opt_, "_")
		local cap 		= self._Cap[id_cap]
		local wrapbox 	= self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),h,-10)
		local ctrltbl	= ctrl
		local handlers = {
			ctrltbl		= ctrltbl, 	-- TABLE of radio options from 1 to (flags.num_opts)
			txttbl 		= txttbl, 	-- TABLE of handlers for the radio options
			cap 		= cap, 		-- handler for text caption element
		}
		flags.etype 	= "radio"
		flags.num_opts 	= num 		-- number of options
		flags.hvtype 	= str 		-- "horz" or "vert"
		flags.path 		= path  	-- MCM menu path
		flags.opt		= opt 		-- MCM option ID
		ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,ctrltbl,v,flags)
	end

	return h
end
function UI_MCM:Callback_Radio(ctrl, path, opt, v, n)
	local value = ctrl[n]:GetCheck()
	--printf("n = %s", n)
	if value then
		for i=1,#ctrl do
			if i ~= n then
				ctrl[i]:SetCheck(false)
			end
		end
		
		local content = self:GetContent(path, opt, v)
		self:CacheValue(path, opt, content[n][1], v)
	else
		ctrl[n]:SetCheck(true)
	end
end

function UI_MCM:Register_BtnAll(xml, handler, path, opt, v, flags)
	local ctrl = xml:Init3tButton("elements:btn_all",handler)
	xml:InitStatic("elements:cap_all",handler)
	
	local id_ctrl = self:Stacker(path, opt, v)
	
	self:Register(ctrl, id_ctrl)
	local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
		self:Callback_BtnAll(ctrl, path, opt, v, flags)
	end
	self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)
end
function UI_MCM:Callback_BtnAll(ctrl, path, opt, v, flags)
	local id = cc(path , opt)
	local group = flags.group
	local value = self:GetValue(path, opt, v, flags)
	
	-- Set same value for identical options of same group
	local function set_in_group(p, group, path, opt, value)
		print_dbg("~set_in_group | current path: %s - target opt: %s", path, opt)
		for i=1,#p do
			local path_ext = path and (path ~= "") and cc(path , p[i].id) or p[i].id
			if p[i].sh then
				print_dbg("~set_in_group | current path: %s - target opt: %s", path_ext, opt)
				if (p[i].id_gr == group) then
					local gr = p[i].gr
					for j=1,#gr do
						if gr[j].id == opt then
							local id_ext = cc(path_ext , opt)
							if check_opt_table(id_ext) then
								print_dbg("-set_in_group | Found match: %s", id_ext)
								self:CacheValue(path_ext, opt, value, gr[j])
							end
						end
					end
				end
			else
				set_in_group(p[i].gr, group, path_ext, opt, value)
			end
		end
	end
	set_in_group(options, group, "", opt, value)
end

function UI_MCM:Register_Preset(ct)
	if ct.presets then
		self.preset:ClearList()
		-- 
		for i=1,#ct.presets do
			self.preset:AddItem( game.translate_string(opt_str_prst .. ct.presets[i]), i)
		end
		if (ct.curr_preset) then
			self.preset:SetText( game.translate_string(opt_str_prst .. ct.curr_preset) )
		end
		self.preset:Show(true)
		self.preset_cap:Show(true)
	else
		self.preset:ClearList()
		self.preset:Show(false)
		self.preset_cap:Show(false)
	end
end
function UI_MCM:Callback_Preset()
	if not (self.last_curr_tree and self.last_path) then
		return
	end
	
	local txt = self.preset:GetText()
	if not (txt and txt ~= "") then
		return
	end
	
	-- Retrieve the preset section
	local pres
	local presets = self.last_curr_tree.presets
	for i=1,#presets do
		if game.translate_string(opt_str_prst .. presets[i]) == txt then
			pres = presets[i]
			break
		end
	end
	
	if pres and ini_pres:section_exist(pres) then
		self.last_curr_tree.curr_preset = pres
		--self:Reset_opt(self.last_curr_tree, self.last_path, { preset = pres })
		
		local n = ini_pres:line_count(pres)
		local result, id, value
		for i=0,n-1 do
			result, id, value = ini_pres:r_line_ex(pres,i,"","")
			
			-- Validate option
			local v = get_opt_table(id)
			if v and v.type then
			
				-- No need to modify options that can't be seen
				local to_hide = v.precondition and (not exec(unpack(v.precondition)))
				if (not to_hide) then
				
					-- Get proper value
					if v.val == 0 then
						
					elseif v.val == 1 then
						value = (value == "true") and true or false
					elseif v.val == 2 then
						value = tonumber(value)
					end
					
					-- Extract path and opt
					local t = str_opt_explode(id)
					local opt = t[#t]
					local path = t[1]
					for i=2,#t-1 do
						path = cc(path , t[i])
					end
					
					-- Cache changes
					self:CacheValue(path, opt, value, v)
				end
			end
		end
		
		-- Update XML elements
		self:Reset_opt(self.last_curr_tree, self.last_path)
		
		-- Update state
		self:UpdatePending()
	end
end

function UI_MCM:Register_Tree(tr, path, group, idx)
	print_dbg("-Register_Tree | tr: %s - path: %s", tr, path)
	local xml = self.xml
	self.key_input = nil
	self.k_binder = nil
	
	if (not self.tree[tr]) then
		self.tree[tr] = {}
	end

	if (not self.tree[tr][path]) then
		self.tree[tr][path] = xml:InitScrollView("main:tree_" .. tr, self.dialog)
		
		--[[
		local pos = self.tree[tr][path]:GetWndPos()
		if tr == 3 then idx = 1 end
		self.tree[tr][path]:SetWndPos(vector2():set( pos.x , pos.y + (25*(idx-1)) ))
		--]]
		
		if (not self.bl[tr]) then
			self.bl[tr] = {}
		end
		
		self.bl[tr][path] = {}
		
		-- Fill tree
		for i=1,#group do
			local _st = xml:InitStatic("main:st_tree", nil)
			
			self.bl[tr][path][i] = xml:InitCheck("elements:btn_list", _st)
			self.bl[tr][path][i]:SetCheck(false)
			
			local txt = xml:InitTextWnd("elements:txt_list", _st)
			txt:SetText( game.translate_string(group[i].text or opt_str_menu .. group[i].id) )
			txt:SetTextColor( clr_tree[tr] )
			
			self.tree[tr][path]:AddWindow(_st, true)
			_st:SetAutoDelete(false)
		end
		
		-- Set Callback for tree buttons
		for i=1,#self.bl[tr][path] do
			local path_i = (path ~= "") and cc(path , group[i].id) or group[i].id
			
			self:Register(self.bl[tr][path][i], ("tree_"..path_i))
			local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
				self:Callback_Tree(tr, path_i, group[i], self.bl[tr][path], i)
			end
			self:AddCallback(("tree_"..path_i), ui_events.BUTTON_CLICKED, _wrapper, self)
		end
	end
	
	self.tree[tr][path]:Show(true)
	
	self.bl[tr][path][1]:SetCheck(true)
	local path_1 = (path ~= "") and cc(path , group[1].id) or group[1].id
	self:Callback_Tree(tr, path_1, group[1], self.bl[tr][path], 1)
end
function UI_MCM:Callback_Tree(tr, path, group, ctrl, i)
	print_dbg("-Callback_Tree | tr: %s - path: %s - index: %s", tr, path, i)
	self.key_input = nil
	self.k_binder = nil
	
	-- Radio buttons behavior
	if (ctrl[i]:GetCheck() == false) then
		ctrl[i]:SetCheck(true)
		return
	end
	for k=1,#ctrl do
		if k ~= i then
			ctrl[k]:SetCheck(false)
		end
	end
	
	-- Hide all sub trees
	for k=tr+1,#self.tree do
		for _,v in pairs(self.tree[k]) do
			v:Show(false)
		end
	end
		
	-- If its an option list, show it
	if group.sh then
		self:Reset_opt(group, path)
		
		-- Caching current options
		self.last_path = path
		if (not self.last_curr_tree) then
			self.last_curr_tree = {}
		end
		empty_table(self.last_curr_tree)
		copy_table(self.last_curr_tree, group)
		
	else
		self:Register_Tree(tr+1, path, group.gr, i)
	end
end


------------------------------------------------------------
-- Utilities
------------------------------------------------------------
function UI_MCM:GetValue(path, opt, v, flags)
-- NOTE: make sure to check for nil values only, since false exists as legit value for commands and check boxes
	local value
	
	if flags and flags.def then

		value = self:GetDefaultValue(path, opt, v)

	elseif flags and flags.preset then
		local pres = flags.preset
		local id = cc(path , opt)
		
		if v.val == 0 then
			value = ini_pres:r_string_ex(pres, id)
		elseif v.val == 1 then
			value = ini_pres:r_bool_ex(pres, id)
		elseif v.val == 2 then
			value = ini_pres:r_float_ex(pres, id)
		end
	end
	
	if (value ~= nil) or (flags and flags.def) then
		if (value ~= nil) and (v.type == "track") then
			value = clamp(value, v.min, v.max)
		end
		self:CacheValue(path, opt, value, v)
	end
	if (value == nil) then
		value = self:GetCurrentValue(path, opt, v)
	end
	if (value == nil) then
		value = self:GetDefaultValue(path, opt, v)
	end
	
	if (value ~= nil) and (v.type == "track") then
		value = clamp(value, v.min, v.max)
	end

	return value
end

function UI_MCM:GetDefaultValue(path, opt, v)
	local id = cc(path , opt)
	local value
	
	if (type(v.def) == "table") then
		value = exec(unpack(v.def))
	else
		value = v.def
	end
	
	-- We cache default values for the first time, so current values rely on them up later
	-- because some default values are randomized and player might not touch them, thus causing randomized effects where they are used in-game
	if (axr_main.config:r_value(opt_section, id, v.val) == nil) and (value ~= nil) then
		axr_main.config:w_value(opt_section, id, value)
		self.Save_AXR = true
	end
	
	return value
end

function UI_MCM:GetCurrentValue(path, opt, v)
	local id = cc(path , opt)
	
	if (opt_temp[id] ~= nil) then
		local _id = s_gsub(id, _opt_, "_")
		if self._Cap[_id] and self._Cap[_id]:IsShown() then
			self._Cap[_id]:TextControl():SetTextColor( clr_o )
		end
		
		return opt_temp[id]
	end
	
	local value
	if v.curr then
		value = exec(unpack(v.curr))
	elseif v.cmd then
		if v.val == 0 then
			value = get_console_cmd(0, v.cmd)
		elseif v.val == 1 then
			value = get_console_cmd(1, v.cmd)
		elseif v.val == 2 then
			value = get_console_cmd(0, v.cmd) --get_console_cmd(2, v.cmd) -- some commands are integers, using get_float will return 0. This is a walkaround
			value = tonumber(value)
			if v.min and v.max then
				value = clamp(value, v.min, v.max)
			end
			value = round_idp(value, v.prec or precision)
		end
	
	else
		value = axr_main.config:r_value(opt_section, id, v.val)
	end

	return value
end

function UI_MCM:GetContent(path, opt, v)
	if v.cmd and (not v.content) then
		local value
		if v.val == 0 then
			value = get_console_cmd(0, v.cmd)
		elseif v.val == 1 then
			value = get_console_cmd(1, v.cmd)
		elseif v.val == 2 then
			value = get_console_cmd(0, v.cmd)	--get_console_cmd(2, v.cmd)
			value = tonumber(value)
			if v.min and v.max then
				value = clamp(value, v.min, v.max)
			end
			value = round_idp(value, v.prec or precision)
		end
		return {{value,tostring(value)}}
		
	elseif (type(v.content[1]) == "function") then
		return exec(unpack(v.content))
	else
		return v.content
	end
end

function UI_MCM:GetOption(id)
	
	local t = str_explode(id,_opt_ )
	local v = options
	for i=1,#t do
		for j=1,#v do
			if v[j].id == t[i] then
				if i == #t then
					v = v[j]
				else
					v = v[j].gr
				end
				break
			end
		end
	end
	return v
end

function UI_MCM:CacheValue(path, opt, value, v)
	local id = cc(path , opt)
	
	-- Do a backup of current values first
	if (opt_backup[id] == nil) then
		opt_backup[id] = self:GetValue(path, opt, v)
		print_dbg("# Backup [%s] = %s", id, opt_backup[id])
	end
	
	-- Cache changed values
	if (value ~= nil) and (value ~= opt_backup[id]) then
		opt_temp[id] = value
		print_dbg("/ Cached [%s] = %s", id, value)
	else
		opt_temp[id] = nil -- no need to cache current values
		print_dbg("~ Cleared cache [%s]", id)
	end
	
	-- Change text color
	local _id = s_gsub(id, _opt_, "_")
	if self._Cap[_id] and self._Cap[_id]:IsShown() then
		if (opt_temp[id] ~= nil) and (opt_temp[id] ~= opt_backup[id]) then
			self._Cap[_id]:TextControl():SetTextColor( clr_o )
		else
			self._Cap[_id]:TextControl():SetTextColor( clr_g1 )
		end
	end
	
	-- Update state
	self:UpdatePending()
	if enable_ui_functors and v and v.on_selection_functor then
		ui_mcm.exec(unpack(v.on_selection_functor),path,opt,value,v)
	end
	-- If the on_selection_functor attribute contains a functor, pass a copy of the same args to it
end

function UI_MCM:Stacker(path, opt, v)
-- This assure that each time a control is created, an unique callback id is given to it
-- Why? because in normal case, jumping between options removes the previous ones constantly, getting back to them will create new controls and assign them to the old ids
-- This is bad because callbacks are still attached to the old controls, any fresh controls that get assigned to those ids will be inactive as a result
-- My solution is this function to generate unique id each time a control is created

	if (not v.stack) then v.stack = 0 end
	v.stack = v.stack + 1
	
	return cc( cc(path , opt) , v.stack)
end

function UI_MCM:UpdatePending()
	local size = size_table(opt_temp)
	if size > 0 then
		self.pending:SetText( strformat( game.translate_string("ui_mm_warning_pending"), size) )
	else
		self.pending:SetText("")
	end
end


------------------------------------------------------------
-- Callbacks
------------------------------------------------------------
function UI_MCM:OnButton_Accept()
	--if self.Need_VidRestart then
	--	self.message_box:InitMessageBox("message_box_yes_no")
	--	self.message_box:SetText(string.format("%s %d% s", game.translate_string("ui_mm_confirm_changes"), 15, game.translate_string("mp_si_sec")))
	--	self.message_box:ShowDialog(true)
	--else
		self:On_Accept()
	--end
end

function UI_MCM:OnButton_Reset()
	if self.last_path and self.last_curr_tree and is_not_empty(opt_temp) then
		local to_reset
		for id, val in pairs(opt_temp) do
			if s_find(id,self.last_path) then
				to_reset = true
				opt_temp[id] = nil
				
				local _id = s_gsub(id, _opt_, "_")
				if self._Cap[_id] and self._Cap[_id]:IsShown() then
					self._Cap[_id]:TextControl():SetTextColor( clr_g1 )
				end
			end
		end
		
		if (to_reset) then
			self:UpdatePending()
			self:Reset_opt(self.last_curr_tree, self.last_path)
			print_dbg("% Sent callback (mcm_option_reset)")
			SendScriptCallback(mcm_option_reset)
		end
	end
	

end

function UI_MCM:OnButton_Default()
	if self.last_path and self.last_curr_tree then
		self:Reset_opt(self.last_curr_tree, self.last_path, { def = true })
		print_dbg("% Sent callback (mcm_option_restore_default)")
		SendScriptCallback(mcm_option_restore_default)
	end
	

end

function UI_MCM:OnButton_Cancel()
	if is_not_empty(opt_temp) then
		self.message_box:InitMessageBox("message_box_yes_no")
		self.message_box:SetText(game.translate_string("ui_mm_discard_changes"))
		self.message_box:ShowDialog(true)
	else
		self:On_Cancel()
	end
end

function UI_MCM:On_Accept()
	self.Changes ={}
	for id, val in pairs(opt_temp) do
	
		local v = self:GetOption(id)
		
		-- Cache the changes
		if (not v.curr) then
			print_dbg("- Saved [%s] := %s", id, val)
			axr_main.config:w_value(opt_section, id, val)
			if key_by_path[id] then
				update_conflict(id, val)
			end
			self.Save_AXR = true
		end
		
		self.Change_Done = true
		self.Changes[id] = true
		-- Execute functors if found
		if v.functor then
			if v.postcondition then
				if exec(unpack(v.postcondition))then
					print_dbg("- Executing postcondition functor of [%s]",id)
					v.functor[#v.functor+1] = val
					exec(unpack(v.functor))
					v.functor[#v.functor] = nil
				end
			else
				print_dbg("- Executing functor of [%s]",id)
				v.functor[#v.functor+1] = val
				exec(unpack(v.functor))
				v.functor[#v.functor] = nil
			end
		end
		
		if v.type == "key_bind" then
			self.key_changed = true
		end
		
		
		-- See if it needs restart
		if v.restart then
			self.Need_Restart = true
		end
		if v.vid then
			self.Need_VidRestart = true
		end
		
		-- Send callback and apply changes
		if v.cmd then
			local cmd_value = val
			if type(cmd_value) == "boolean" then
				if v.bool_to_num then
					cmd_value = cmd_value and "1" or "0"
				else
					cmd_value = cmd_value and "on" or "off"
				end
			end
			
			print_dbg("- Saved CMD [%s] := %s", id, cmd_value)
			exec_console_cmd(v.cmd .. " " .. cmd_value)
			self.Save_CFG = true
		end
	end
	

	
	-- Save axr_options
	if self.Save_AXR then
		axr_main.config:save()
		self.Save_AXR = false
	end
	

	
	-- appdata
	if self.Save_CFG then
		print_dbg("- Saved CFG")
		exec_console_cmd("cfg_save")
		--exec_console_cmd("cfg_save tmp")
	end

	if level.present() and self.Change_Done then
		print_dbg("% Sent callback (on_option_change)")
		SendScriptCallback("on_option_change", true)
	end
	if AddScriptCallback and self.Change_Done then
		print_dbg("% Sent callback (mcm_option_change)")
		SendScriptCallback("mcm_option_change", self.Changes )
	end

	-- Clear cache
	empty_table(opt_temp)
	empty_table(opt_backup)
	
	if self.key_changed then --resort the conflict key list.
		self.key_changed = false
		table.sort(options[1].gr[2].gr[3].gr, function(a,b)
			local t = {slide_kb = 1, desc_kb = 2, desc_kb2 = 3}
			if t[a.id] and t[b.id] then
				return t[a.id] < t[b.id]
			end
			if t[a.id] and not t[b.id] then
				return true
			end
			if (not t[a.id]) and t[b.id] then
				return false
			end
			return display_key(get(a.kb_path)) < display_key(get(b.kb_path))
		end)
		init_opt_coder()
	end

	
	-- Exit
	self:On_Cancel()
end

function UI_MCM:On_Cancel()

	self.owner:ShowDialog(true)
	self:HideDialog()
	self.owner:Show(true)
	
	-- Restart vid
	if self.Need_VidRestart then
		exec_console_cmd("vid_restart")
	end
	
	if self.Need_Restart then
		self.owner:SetMsg( game.translate_string("ui_mm_change_done_restart") , 7 )
		self.message_box:InitMessageBox("message_box_restart_game")
		self.message_box:ShowDialog(true)
		
	elseif self.Change_Done then
		self.owner:SetMsg( game.translate_string("ui_mm_change_done") , 5 )
	end
	
	self.Change_Done = false
	self.Need_VidRestart = false
	self.Need_Restart = false
	self.Save_CFG = false

	
	Unregister_UI("UI_MCM")
end

function UI_MCM:On_Discard()
	empty_table(opt_temp)
	if (self.last_path and self.last_curr_tree) then
		self:UpdatePending()
		self:Reset_opt(self.last_curr_tree, self.last_path)
		print_dbg("% Sent callback (mcm_option_discard)")
		SendScriptCallback(mcm_option_discard)
	end
	self:On_Cancel()
end

function UI_MCM:OnKeyboard(dik, keyboard_action)
	local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
	if (res == false) then
		local bind = dik_to_bind(dik)
		if keyboard_action == ui_events.WINDOW_KEY_PRESSED then
		
			if dik == DIK_keys.DIK_ESCAPE then
				if self.k_binder then
					self.k_binder(-1)
				else
					self:OnButton_Cancel()
				end
			elseif self.k_binder and display_key(dik) then
				self.k_binder(dik)
			end
		end
	end
	if self.k_binder and ((time_continual() - self.k_timer)  > 500) then --clear k_binder on any other input after half a second.
		self.k_binder()
	end
	return res
end




function trader_cond(x)
	if x == 'get' then
		-- printf('@@@ returning %s', alife_storage_manager.get_state().trader_buy_condition_override or "0 (DEFAULT)")
		return alife_storage_manager.get_state().trader_buy_condition_override or 0
	else
		-- printf('@@@ setting %s', opt_temp["gameplay/economy_diff/condition_buy_override"] or '0 (DEFAULT)')
		alife_storage_manager.get_state().trader_buy_condition_override = opt_temp["gameplay/economy_diff/condition_buy_override"] or 0
	end
end

function store_in_save()
	printf("MCM Error: dph_mcm_save_storage.script not found")
end

if dph_mcm_save_storage and dph_mcm_save_storage.register_module then
	store_in_save = dph_mcm_save_storage.register_module
end

function on_game_start()
    RegisterScriptCallback("on_key_release",on_key_release)
    RegisterScriptCallback("on_key_press",on_key_press)
end

------------------------------------------------------------
-- Tutorial:Table of Contents:
------------------------------------------------------------
--[[
	Use the [-] in the margin in notpad++ to colapse sections for easier
	navigation
	
	1. How to read these options in your script (RavenAscendant)
	2. How to add new options (Tronex orginal turotial from ui_options)
	3. How to make your script talk to MCM
	4. Examples

]]--

------------------------------------------------------------
-- Tutorial: How to read these options in your script:
------------------------------------------------------------
--[[
	Feilds in [backets]  are described in "How to add new options"
	
	First a bit about setting up your options. Nine times out of ten you don't need any functors.
		MCM will read the curent value of the setting from axr_options with out a [curr] functor and
		will write values to axr_options if no [functor] is provided. For simple global settings this 
		will be more than adequate.
	
	The easiest way to read your setting is call ui_mcm.get(path) where path is the id fields of 
		the nested tables down to the option in the table you returned in on_mcm_load() . Mostlikely 
		this will take the form of "modname/settingname"  but you can break your settings into multiple
		panels if you want resulting in a loinger path. see the options section of axr_configs for how 
		anomaly options menu translates into paths, same system is used here.
		ui_mcm.get(path) is cached and fails back to the value you set in [def=] 
	
	Just like ui_options when MCM applies a settings change it sends an on_option_change callback
		you can use this to do a one time read of your options into variables in your script.
		you can either get the values with ui_mcm.get(path) or read them directly from axr_configs
		like so:
			axr_main.config:r_value("mcm", path, type,default) --see _g for how r_value functions.
	
	
	Examples of when you might want to use functors: 
		Saving mod settings to the save game file instead of globaly to axr_configs
		You are building your settings dynaicaly can't rely on the path being consistant.
		You otherwise like to over complicate things.

]]--


------------------------------------------------------------
-- Tutorial: How to add new options:
------------------------------------------------------------
--[[

	The only thing changed from this as compared to the version in ui_options is changing all ocurances of ui_mm_ to ui_mcm_ 
	------------------------------------------------------------------------------------------------
	Option name:
		script will read option name (id) and show it automatically, naming should be like this: "ui_mcm_[tree_1]_[tree_2]_[tree_...]_[option]"
		[tree_n] and [option] are detemined from option path inside the table
			Example: options["video"]["general"]["renderer"] name will come from "ui_mcm_video_general_renderer" string
			
	------------------------------------------------------------------------------------------------
	Option description:
		option description can show up in the hint window if its defined by its name followed by "_desc"
			Example: option with a name of "ui_mcm_video_general_renderer" will show its hint if "ui_mcm_video_general_renderer_desc" exists
		
	
	------------------------------------------------------------------------------------------------
	Parameters of option Tree:
	------------------------------------------------------------------------------------------------
	
	- [id]
	- Define: (string)
		To give a tree its own identity
	
	- [sh]
	- Define: (boolean)
		used to detemine that the sub-tree tables are actual list of options to set and show
		
	- [text]
	- Define: (string)
		To over ride the display text for the tree in the tree select
		
	- [precondition]
	- Define: ( table {function, parameters} )
		don't show tree options if its precondition return false
		
	- [output]
	- Define: (string)
		Text to show when precondition fails
		
	- [gr]
	- Define: ( table { ... } )
		Table of a sub-tree or options list
		
	- [apply_to_all]
	- Define: (boolean)
		when you have options trees with similar options and group, you can use this to add "Apply to All" button to each option
		clicking it will apply option changes to this option in all other trees from same group
		you must give these a tree a group id
		
	- [id_gr]
	- Define: (string)
		allows you to give options tree a group id, to connect them when you want to use "Apply to all" button for options
	
	------------------------------------------------------------------------------------------------
	Parameters of options:
	------------------------------------------------------------------------------------------------
	
	----------------------
	Critical parameters:
	--------------------
	These parameters must be declared for elements
	
	[id]
	- Define: (string)
		Option identity/name.
		Option get stored in axr_main or called in other sripts by its path (IDs of sub trees and option):
			Example: ( tree_1_id/tree_2_id/.../option_id ) 
		The top id in the table you return to MCM (tree_1_id in the above example) should be as unique as 
			posible to prevent it from conflicting with another mod.
	
	[type]
	- Define: (string)
	- Possible values:
		- Option elements:
				"check"        	: Option, check box, either ON or OFF
				"list"         	: Option, list of strings, useful for options with too many selections
				"input"        	: Option, input box, you can type a value of your choice
				"radio_h"   	: Option, radio box, select one out of many choices. Can fit up to 8 selections (Horizental layout)
				"radio_v"   	: Option, radio box, select one out of many choices. Can fit up any number of selections (Vertical layout)
				"track"       	: Option, track bar, easy way to control numric options with min/max values (can be used only if [val] = 2)
				"key_bind"		: Option, button that registers a keypress after being clicked. (See suplimental instructions below)
		- Support elements:
				"line"         	: Support element, a simple line to separate things around
				"image"    	   	: Support element, 563x50 px image box, full-area coverage
				"slide"   		: Support element, image box on left, text on right
				"title"   		: Support element, title (left/right/center alignment)
				"desc"   		: Support element, description (left alignment)
		
	
	----------------------
	Dependable parameters:
	----------------------
	These parameters must be declared when other specific parameters are declared already. They work along with them
		
	[val]
	- Define: (number)
	- Used by: option elements: ALL
		Option's value type: 0. string | 1. boolean | 2. float
		It tells the script what kind of value the option is storing / dealing with

	[cmd]:
	- Define: (string)
	- Used by: option elements: ALL (needed if you want to control a console command)
		Tie an option to a console command, so when the option value get changed, it get applied directly to the command
		The option will show command's current value 
		NOTE:
			cmd options don't get cached in axr_options, instead they get stored in appdata/user.ltx
			[def] parameter is not needed here since we engine applies default values to commands if they don't exist in user.ltx automatically
		
	[def]
	- Define: (boolean) / (number) / (string) / ( table {function, parameters} )
	- Used by: option elements: ALL (not needed if [cmd] is used)
		Default value of an option
		when no cached values are found in axr_options, the default value will be used
	
	[min]
	- Define: (number)
	- Used by: option elements: "input" / "track": (only if [val] = 2)
		Minimum viable value for an option, to make sure a value stays in range
		
	[max]
	- Define: (number)
	- Used by: option elements:  "input" / "track": (only if [val] = 2)
		Maximum viable value for an option, to make sure a value stays in range
		
	[step]
	- Define: (number)
	- Used by: option elements: "track": (only if [val] = 2)
		How much a value can be increased/decreased in one step
		
	[content]
	- Define: ( table {double pairs} ) / ( table {function, parameters} )
	- Used by: option elements: "list" / "radio_h" / "radio_v":
		Delcares option's selection list
		Pairs: { value of the selection, string to show on UI }
			Example: content= { {0,"off"} , {1,"half"} , {2,"full"}}
			So the list or radio option will show 3 selections (translated strings): (ui_mcm_lst_off) and (ui_mcm_lst_half) and (ui_mcm_lst_full)
			When you select one and it get applied, the assosiated value will get applied
			So picking the first one will pass ( 0 )
		Because all lists and radio button elements share the same prefix, "ui_mcm_lst_" it is important that you not use common words like
			the ones in the example above. Make your element names unique.
		
	[link]
	- Define: (string)
	- Used by: support elements: "image" / "slide"
		Link to texture you want to show
		
	[text]
	- Define: (string)
	- Used by: support elements: "slide" / "title" / "desc"
		String to show near the image, it will be translated
		
	----------------------
	Optional parameters:
	----------------------
	These parameters are completely optionals, and can be used for custom stuff
	
	[force_horz]
	- Define: (boolean)
	- Used by: option elements: "radio_h"
		Force the radio buttons into horizental layout, despite their number
		
	[no_str]
	- Define: (boolean)
	- Used by: option elements: "list" / "radio_h" / "radio_v" / "track"
		Usually, the 2nd key of pairs in content table are strings to show on the UI, by translating "opt_str_lst_(string)"
		when we set [no_str] to true, it will show the string fromm table as it is without translations or "opt_str_lst_"
		For TrackBars: no_str won't show value next to the slider
		
	[prec]
	- Define: (number)
	- Used by: option elements: "track"
		allowed number of zeros in a number
		
	[precondition]
	- Define: ( table {function, parameters} )
	- Used by: option elements: ALL
		Show the option on UI if the precondition function returns true
	
	[functor]
	- Define: ( table {function, parameters} )
	- Used by: option elements: ALL
		Execute a function when option's changes get applied
		The value of the option is added to the end of the parameters list.
		
	[postcondition]
	- Define: ( table {function, parameters} )
	- Used by: option elements: ALL, with the defined functor
		Option won't execute its functor when changes are applied, unless if the postcondition function returns true

	[ui_hook_functor]
	- Define: ( table {function, parameters} )
	- Used by: option elements: ALL except keybind, with the defined functor
	- Used by: support elements: ALL
		Passes handling elements and metadata back to the defined functor for UI customization
		For ADVANCED scripting use only - see below
		
	[on_selection_functor]
	- Define: ( table {function, parameters} )
	- Used by option elements: ALL, with the defined functor
		Execute a defined functor upon any live selection of a new option value
		For ADVANCED scripting use only - see below

	[curr]
	- Define: ( table {function, parameters} )
	- Used by: option elements: ALL
		get current value of an option by executing the declared function, instead of reading it from axr_options.ltx
		
	[hint]  (as of MCM 1.6.0 this will actualy show _desc strings)
	- Define: (string)
	- Used by: option elements: ALL
		Override default name / desc rule to replace the translation of an option with a custom one, should be set without "ui_mcm_" and "_desc"
			Example: { hint = "alife_warfare_capture"} will force the script to use "ui_mcm_alife_warfare_capture" and "ui_mcm_alife_warfare_capture_desc" for name and desc of the option
	
	[clr]
	- Define: ( table {a,r,b,g} )
	- Used by: support elements: "title" / "desc"
		determines the color of the text
		
	[stretch]
	- Define: (boolean)
	- Used by: support elements: "slide"
		force the texture to stretch or not
		
	[pos]
	- Define: ( table {x,y} )
	- Used by: support elements: "slide"
		custom pos for the texture
		
	[size]
	- Define: ( table {w,z} )
	- Used by: support elements: "slide"
		custom size for the texture
		
	[align]
	- Define: (string) "l" "r" "c"
	- Used by: support elements: "title"
		determines the alignment of the title
		
	[spacing]
	- Define: (number)
	- Used by: support elements: "slide"
		height offset to add extra space
		
	[borderless]
	- Define: (boolean)
	- Used by: support elements: "slide"
		disables the border lines above and below the slide
--]]

------------------------------------------------------------
-- Tutorial: How to make your script talk to MCM:
------------------------------------------------------------

--[[
	MCM looks for scripts with names ending in mcm: *mcm.script you can use an _ to sperate it from the 
		rest of the name of your script but it isn't necessary.
	In those scripts MCM will execute the function on_mcm_load()
	In order for options to be added to MCM, on_mcm_load() must return a valid options tree
		as described in the tutorial here, used in the ui_options script and shown in the examples below
	An aditioanl retun value of a string naming a collection is optional. The string will be used to create a catagory to which the	
		the options menues of mods returning the same collection name will be added to. This is to allow for 
		modular mods to have the settings for each module be grooped under a common heading. Note the collection name becomes the root
		name in your settings path and translation strings. As a root name care should be taken to ensure it will not conflict with another 
		mod.

	
]]--

---------------------------------------------------------------------------------------
-- Tutorial: Using dph-hcl's script for save game specific MCM options
---------------------------------------------------------------------------------------

--[[
	dph-hcl's orginal script from https://www.moddb.com/mods/stalker-anomaly/addons/151-mcm-13-mcm-savefile-storage 
		is included un altered and can be used as described and documented in thier mod and script
	
	Aditionaly for convinence the function has been aliased here as ui_mcm.store_in_save(path)
		this function can be called safely as MCM will simply print an error if dph-hcl's script is missing

	To make an option be stored in a save game instead of globaly call ui_mcm.store_in_save(path)
	 	path can be a full option path like is used by ui_mcm.get(path) or a partial path
	If a partial path is used all options that caontain that path will be stored in the savegame
		partial paths must start with a valid root and cannot end with a /

	In the second example below the second checkbox in the second options menu would be stored buy
		ui_mcm.store_in_save("example_example/example_two/2check2")
	In the same example storing all options (both checks) in the first option menu would be:
		ui_mcm.store_in_save("example_example/example_one")
	Lastly storing all of the options for your mod would look like: 
		ui_mcm.store_in_save("example_example")
	
	ui_mcm.store_in_save(path) can be called at any time. The easyiest is probably in on_mcm_load()
		however it could be done as late as on_game_start() if one wanted to have an MCM option for global vs save specific options storing
			(calling ui_mcm.get(path) in on_mcm_load() is a bad idea don't do that )
]]--



---------------------------------------------------------------------------------------
-- Tutorial: Additional information on key_bind
---------------------------------------------------------------------------------------

--[[
	Key binds are gathered into two meta lists for the users convienance. This means it is very important that your translation strings
		clearly identify what the key does and ideally it should be clear what addon the keybiind is from.
	
	The value stored by the key bind is the DIK_keys value of the key. Same number that will be given to the key related callbacks.

	val must be set to 2 and is still manditory.
	
	curr and functor  are not curently supported. Post an issue on github describing the usecase you had for them, if it's cool enough they might get fixed.

	Old (pre 1.6.0) versions of MCM will not display key_bind and calling ui_mcm.get for it will return nil, take that into acount if you want reverse compatablity. 
	
]]--


---------------------------------------------------------------------------------------
-- Tutorial: Additional Key Bind utilities
---------------------------------------------------------------------------------------

--[[
	MCM tracks the held status of the control and shift keys as well as a flag that is true when neither is pressed
		ui_mcm.MOD_NONE  ui_mcm.MOD_SHIFT and ui_mcm.MOD_CTRL
		ui_mcm.get_mod_key(val) will return the above flags based on val: 0:MOD_NONE 1:MOD_SHIFT and 2:MOD_CTRL
		If these somehow get latched they reset when Escape is pressed. Please report cases of latching.
	
	MCM provides functions for detecting key double taps and keys that are held down, and single key presses that do not go on to be double or long presses.
		ui_mcm.double_tap(id, key, [multi_tap]) should be called from on_key_press callback after you have filtered for your key
			id: 	should be a unique identifier for your event, scriptname and a number work well:"ui_mcm01"
			key: 	is of course they key passed into the on_key_press callback.
			multi_tap: if true timer is updated instead of cleared allowing for the detection of triple/quad/ect taps
			returns: true for a given id and key if less than X ms has elapsed since the last time it was called with that id and key (X is a user configurable value between 100ms and 1000 ms
					 returns false otherwise. 
					 If multi_tap is false timer is reset when true is returned preventing the function from returning true twice in a row
					 If multi_tap is true the function will return true any time the gap between a call and the one before is within the window.
			
		ui_mcm.key_hold(id, key, [repeat]) should be called from on_key_hold callback after you have filtered for your key
			id: 	should be a unique identifer for your event, scriptname and a number work well:"ui_mcm01"
			key: 	is the key passed into the on_key_hold callback.
			repeat: Optional. time in seconds. If the key continues to be held down will return true again after this many seconds on a cycle.
			
			when called from the on_key_hold callback it will return true after the key has been held down for Y ms (determined by applying a user defined multiplier to X above) and then again every repeat seconds if repeat is provided. sequence resets when key is released.

		ui_mcm.simple_press(id, key, functor) should be called from on_key_press callback after you have filtered for your key
			id: 	should be a unique identifier for your event, scrip name and a number work well:"ui_mcm01"
			key: 	is the key passed into the on_key_press callback.
			function: table {function, parameters}, to be executed when it is determined that the press is not long or double (or multi press in general)
			
			Unlike the other two this does not return any thing but instead you give it a function to execute. Using this function you gain exclusivity, your event won't fire when the key is double(multi) taped or held (long press), at the cost of a small bit of input delay. This delay is dependent on the double tap window the used defines in the MCM Key Bind settings.

	The following option entries have translation stings provided by MCM and are setup to be ignored by pre 1.6.0 versions of MCM
		Note the keybind conflict identification in MCM does NOT look for these and reports conflict on the keybind value alone.
		
		With shift and control, radio buton style
	        {id = "modifier", type = ui_mcm.kb_mod_radio, val = 2, def = 0, hint = "mcm_kb_modifier" , content= { {0,"mcm_kb_mod_none"} , {1,"mcm_kb_mod_shift"} , {2,"mcm_kb_mod_ctrl"},{3,"mcm_kb_mod_alt"}}},
		With shift and control, list style
	        {id = "modifier", type = ui_mcm.kb_mod_list, val = 2, def = 0, hint = "mcm_kb_modifier" , content= { {0,"mcm_kb_mod_none"} , {1,"mcm_kb_mod_shift"} , {2,"mcm_kb_mod_ctrl"},{3,"mcm_kb_mod_alt"}}},
		
		
		Single double or long press,  , radio buton style
            {id = "mode", type = ui_mcm.kb_mod_radio, val = 2, def = 0, hint = "mcm_kb_mode" , content= { {0,"mcm_kb_mode_press"} , {1,"mcm_kb_mode_dtap"} , {2,"mcm_kb_mode_hold"}}},
		Single double or long press,  , radio buton style
            {id = "mode", type = ui_mcm.kb_mod_list, val = 2, def = 0, hint = "mcm_kb_mode" , content= { {0,"mcm_kb_mode_press"} , {1,"mcm_kb_mode_dtap"} , {2,"mcm_kb_mode_hold"}}},

	An example script making use of all of these can be found at: https://github.com/RAX-Anomaly/MiniMapToggle/blob/main/gamedata/scripts/mini_map_toggle_mcm.script
]]--

------------------------------------------------------------
-- Tutorial: Examples:
------------------------------------------------------------

-- these examples can all be copied to a blank script example_mcm.script and ran.

-- A simple menu with a title slide and check boxes.
--[[

	function on_mcm_load()
		op = { id= "example_example"      	 	,sh=true ,gr={
				{ id= "slide_example_example"				 ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_title_example_example"		,size= {512,50}		,spacing= 20 },
				{id = "check1", type = "check", val = 1, def = false},
				{id = "check2", type = "check", val = 1, def = false},
				}
			}
			
		return op
	end
]]--

-- A a tree with a root containing three menues with a title slide and check boxes. 
--[[

	function on_mcm_load()
		op =  { id= "example_example"      	 	, ,gr={
			
						{ id= "example_one"      	 	,sh=true ,gr={
							{ id= "slide_example_example"				 ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_title_example_example"		,size= {512,50}		,spacing= 20 },
							{id = "1check1", type = "check", val = 1, def = false},
							{id = "1check2", type = "check", val = 1, def = false},
							}
						},
						{ id= "example_two"      	 	,sh=true ,gr={
							{ id= "slide_example_example"				 ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_title_example_example"		,size= {512,50}		,spacing= 20 },
							{id = "2check1", type = "check", val = 1, def = false},
							{id = "2check2", type = "check", val = 1, def = false},
							}
						},
						{ id= "example_three"      	 	,sh=true ,gr={
							{ id= "slide_example_example"				 ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_title_example_example"		,size= {512,50}		,spacing= 20 },
							{id = "3check1", type = "check", val = 1, def = false},
							{id = "3check2", type = "check", val = 1, def = false},
							}
						},
					}
				}
					
			
		return op
	end
]]--

-- Two scripts with a simple menu with a title slide and check boxes, that will be added to a collection named "collection_example"
--[[
	-- example1_mcm.script
	function on_mcm_load()
		op = { id= "first_example"      	 	,sh=true ,gr={
				{ id= "slide_first_example"				 ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_title_first_example"		,size= {512,50}		,spacing= 20 },
				{id = "check1", type = "check", val = 1, def = false},
				{id = "check2", type = "check", val = 1, def = false},
				}
			}
			
		return op, "collection_example"
	end
	
	-- example2_mcm.script.
	function on_mcm_load()
		op = { id= "second_example"      	 	,sh=true ,gr={
				{ id= "slide_second_example"				 ,type= "slide"	  ,link= "AMCM_Banner.dds"	 ,text= "ui_mcm_title_second_example"		,size= {512,50}		,spacing= 20 },
				{id = "check1", type = "check", val = 1, def = false},
				{id = "check2", type = "check", val = 1, def = false},
				}
			}
			
		return op, "collection_example"
	end
]]--

--[[=====================================================================
    Tutorial: UI Functors
    *** ADVANCED SCRIPTING USE ONLY ***
-- ======================================================================

	The UI functors "ui_hook_functor" and "on_selection_functor", respectively, pass along UI element handlers and a trap for unsaved value changes. They are recommended only for advanced scripters who fully understand how to work with UI elements and want to customize their menu beyond what the template can achieve.
	These functors allow for dynamic customizations to MCM's UI elements at the element container level in response to user interactions. They can also compeltely hose your addon's entire MCM menu with a single error.

	This is not a power to be used lightly. If you use these, you are assumed to know what you're doing. Please don't bug us with how-to questions.	

	- [ui_hook_functor]
	- Define: ( table {function, parameters} )
	- Used by: option elements: ALL (except keybind), with the defined functor
	- Used by: support elements: ALL, with the defined functor

	- Parameters passed: anchor, handlers, attrs, flags
		Execute a function upon the initial registration of a UI element that occurs during on_mcm_load.
		anchor 		- empty static container to use as an anchor for other elements
		handlers	- table containing any necessary UI control handlers, varies by element type
		attrs		- table of MCM attributes for the menu option
		flags 		- flags.etype is always a string with the element type, other metadata varies by type
		As with other functor attributes, the value of the "parameters" option in the table is added to the end of the parameters list.

	- [on_selection_functor]
	- Define: ( table {function, parameters} )
	- Used by option elements: ALL, with the defined functor
	- Parameters passed: path, opt, value, attrs
		Execute a function on any unsaved/uncommitted change to an option value. Allows realtime response to user selections.
		path 		- MCM path to changed option
		opt 		- ID of the changed option
		value 		- value of the uncommitted change
		attrs 		- table of MCM attributes for the menu option
		As with other functor attributes, the value of the "parameters" option in the table is added to the end of the parameters list.
	
	New supporting callbacks:
	mcm_option_reset 			- 	sent from OnButton_Reset
	mcm_option_restore_default 	- 	sent from OnButton_Default
	mcm_option_discard 			- 	sent from On_Discard

	These callbacks all fire on their respective events resulting in cancellation of pending MCM changes. This lets you clear your own table of changes or do any other necessary cleanup at the end of the MCM session.

	Each menu element type has its own set of handlers and flags that are passed in these two tables. They are documented below.

	In addition to those listed, all elements pass the flag "etype" containing the name of the element (in brackets below).

	SUPPORT ELEMENTS
	[line]
		Handlers
		- line: Static container for the line element and its texture

	[image]
		Handlers
		- pic: Static container for the image element and its texture

	[slide]
		Handlers
		- pic: Static container for the slide image element and its texture
		- txt: TextWnd container for the slide label text

	[title]
		Handlers
		- title: Static container for the title element text

	[desc]
		Handlers
		- desc: Static container for the description element text

	OPTION ELEMENTS
	All Option elements pass the following Flags:
		- path: MCM menu path to the option
		- opt: MCM ID for the option

	[check]
		Handlers
		- cap: Static container for the localized text caption
		- ctrl: Static container for the checkbox input control

	[list]
		Handlers
		- cap: Static container for the localized text caption
		- ctrl: Static container for the dropdown list input control

	[input]
		Handlers
		- cap: Static container for the localized text caption
		- ctrl: Static container for the input box control

	[track]
		Handlers
		- cap: Static container for the localized text caption
		- ctrl: Static container for the track input control

	[radio]
		Handlers
		- cap: Static container for the localized text caption
		- txt: Table of TextWnd containers for the radio options
		- ctrltbl: Table of radio button input controls
		Flags
		- num_opts: the number of radio button options in the above tables
		- hvtype: string "horz" or "vert" denoting layout style

-- ======================================================================
    Tutorial: UI Functors: Example:
-- ======================================================================
	You should be very familiar with how to work with UI containers in Anomaly before going any further.

	This is a very simple example of how to use the UI functors. Assume this script is saved as element_test_mcm.script.

	1. MCM calls do_ui_hook_functor for each of the menu elements that have it defined: a slide and a checkbox

	2. do_ui_hook_functor creates a new empty static anchored on the image element to contain the icon texture, stores the handlers for it and the slide text, and changes the caption text for the checkbox

	3. once during init, and each time the user clicks the checkbox, the icon and slide text will change in response

-- ====================================================================]]

--[[
	function on_mcm_load()
		op = {id= "ui_functor_test", sh=true, gr={
				{id = "img_container", type= "slide",
					ui_hook_functor= {element_test_mcm.do_ui_hook_functor}},

				{id = "checkbox", type= "check", val = 1, def = true, hint = "",
					ui_hook_functor= {element_test_mcm.do_ui_hook_functor}, 
					on_selection_functor= {element_test_mcm.do_on_selection_functor}},
				}
			}
		return op
	end

	xml = CScriptXmlInit()
	xml:ParseFile("ui_mcm.xml")
	local wnd,txt

	function do_on_selection_functor(path, opt, value, attrs)
		-- This function is called every time the user makes a selection or change
		-- We also call it manually below, once during init, to set the default texture
		if not (wnd and txt) then return end
		local primary = value and true or false
		if primary then
			wnd:InitTexture("ui_inGame2_PDA_icon_Primary_mission")
			txt:SetText("Primary Mission icon")
		else
			wnd:InitTexture("ui_inGame2_PDA_icon_Secondary_mission")
			txt:SetText("Secondary Mission icon")
		end
	end

	function do_ui_hook_functor(anchor, handlers, attrs, flags)
		-- This function should do any first-time setup for each menu option
		if not (anchor and handlers and flags) then return end
		local etype = flags and flags.etype
		if etype == "slide" then
			wnd = xml:InitStatic("elements:image",anchor)
			wnd:SetWndSize(vector2():set(24,28))
			local pos = wnd:GetWndPos()
			wnd:SetWndPos(vector2():set(pos.x, pos.y + 28))
			txt = handlers.txt
			local curr_state = ui_mcm.get("ui_functor_test/checkbox")
			do_on_selection_functor(nil,nil,curr_state)
		elseif etype == "check" then
			local newtext = "Click the checkbox to toggle the icon type"
			local cap = handlers.cap
			cap:TextControl():SetText(newtext)
		end
	end

--]]