Quantcast
----------------------------------------------
-- Global variables
----------------------------------------------

KarmaData = {};
-- Karma data is stored in the following tree: (v10)
--	KarmaData = {};
--		REALMS = {};
--			["Proudmoore"] = {}
--				FACTIONLIST = {};
--					["Alliance"] = {}
--						CHARACTERLIST = {};
--							["Amadea"] = {}
--							...
--						CHARACTER_IDS = {};
--							[1] = "Amadea",
--							...
--						MEMBERBUCKETS = {[A] = {} ... [Z] = {}};
--	(20400.x	)			ALTGROUPS = {
--							[1] = { ID = 1, AL = { "Foo", "Baz", "Bar" } },
--							[2] = { ID = 2, AL = { "Tick", "Trick", "Track" } },
--							...
--						}
--						QUESTNAMES = {};
--		REALM_IDS = {};
--			[1] = "Proudmoore",
--			[2] = "...",
--			...

--		COMMON = {};
--			REGIONNAMES = {};
--			ZONENAMES = {};
--			FACTIONLIST = {};
--				<Faction> = {};
--					QUESTNAMES = {};
--					QUESTINFOS = {};
--			<LOCALE>
--				TALENTS = {}

-- Karma data is stored in the following tree: (v11)
--	KarmaAvEnK<Faction> = {};
--		REALMS = {};
--			["Proudmoore"] = {}
--				... (like above)
--
-- The switch is so that all faction data can easily be pushed to/pulled from the LOD modules

-- v12: moved XFACTIONHOLD to KarmaData, so the XFaction-DB doesn't need to be loaded on XFaction info
-- v13: added indirect keys, so MEMBERBUCKETS can account cross-realm at all


local	KarmaObj = KarmaAvEnK;
local	KOH = KarmaObj.Helpers;

-----------------------------------------
-- Low Level Functions
-----------------------------------------

function	Karma_NilToEmptyString(Input)
	if (Input ~= nil) then
		return Input;
	else
		return "";
	end
end

function	Karma_NilToString(value)
	if (value == nil) then
		return "<nil>";
	end
	return value;
end

function	Karma_NilToZero(Input)
	-- KarmaChatDefault("N20: " .. Karma_NilToEmptyString(Input));
	if (Input ~= nil) then
		return Input;
	else
		return 0;
	end
end

function	Karma_CopyTable(table)
	local	copyoftable = {};

	local	index, value;
	for index, value in pairs(table) do
		if (type(value) ~= "table") then
			copyoftable[index] = value;
		else
			copyoftable[index] = Karma_CopyTable(value);
		end
	end

	return copyoftable;
end

function	Karma_TableMerge2Into1(table1, table2)
	-- degenerated cases: no first table
	if (#table1 == 0) then
		return Karma_CopyTable(table2);
	end

	-- first table not empty, second is:
	if (#table2 == 0) then
		return Karma_CopyTable(table1);
	end

	local index, value;

	-- depending on size of the tables, walk one and insert into other
	-- special case: very short table2
	if (#table2 < 3) then
		-- get the three values
		local i = 1;
		local val1, val2, val3;
		for index, value in pairs(table2) do
			if (i == 1) then
				val1 = value;
			end
			if (i == 2) then
				val2 = value;
			end
			if (i == 3) then
				val3 = value;
			end
			i = i + 1;
		end
		-- check if already contained
		for index, value in pairs(table1) do
			if (value == val1) then
				val1 = nil;
			end
			if (value == val2) then
				val2 = nil;
			end
			if (value == val3) then
				val3 = nil;
			end
		end
		-- if not contained, insert
		if (val1 ~= nil) then
			table.insert(table1, val1);
		end
		if (val2 ~= nil) then
			table.insert(table1, val2);
		end
		if (val3 ~= nil) then
			table.insert(table1, val3);
		end

		return Karma_CopyTable(table1);
	end

	-- now to the ugly part...
	-- sure this would be helluva lot nicer, if we had them sorted... not today. :D
	local tablelarge, tablesmall;
	if (#table1 >= #table2) then
		tablelarge = table1;
		tablesmall = table2;
	else
		tablelarge = table2;
		tablesmall = table1;
	end

	-- walk the large table, and check for each value, if it's in the small, otherwise insert
	local table3 = {};
	for index, value in pairs(tablesmall) do
		local key = "K"..index;
		table3[key] = {};
		table3[key].ix = index;
		table3[key].val = value;
		table3[key].found = 0;
	end

	local index1, index2, value1, value2;
	for index1, value1 in pairs(tablelarge) do
		-- check if value1 also in tablesmall, if yes, remove
		for index2, value2 in pairs(tablesmall) do
			if (value1 == value2) then
				local key = "K"..index2;
				table3[key].found = 1;
				break;
			end
		end
	end

	-- now tablesmall should only contain values that are not in tablelarge
	-- just throw them in
	for index, value in pairs(table3) do
		if (value.found == 0) then
			table.insert(tablelarge, value.val);
		end
	end

	return Karma_CopyTable(tablelarge);
end

local	Stacks = {};

function	Karma_FieldInitialize(dict, field, initialvalue, bSilent, bCopy)
	KarmaObj.ProfileStart("Karma_FieldInitialize");

	if (dict == nil) or (field == nil) or (type(dict) ~= "table") then
		if (type(initialvalue) ~= "table") then
			KarmaChatDebugFallbackSecondary(KARMA_MSG_FIELDINIT_ERROR_VALUE .. initialvalue);
		else
			KarmaChatDebugFallbackSecondary(KARMA_MSG_FIELDINIT_ERROR_VALUE .. KARMA_MSG_FIELDINIT_ERROR_TABLE);
		end

		local backtrace = debugstack();
		KarmaChatDebugFallbackSecondary("CB: " .. backtrace);
	elseif (dict[field] == nil) then
		if (bCopy and (type(initialvalue) == "table")) then
			dict[field] = Karma_CopyTable(initialvalue);
		else
			dict[field] = initialvalue;
		end

		if (not bSilent) then
			if (type(initialvalue) ~= "table") then
				local valstr = initialvalue;
				if (valstr == true) then
					valstr = "true";
				elseif (valstr == false) then
					valstr = "false";
				elseif (valstr == nil) then
					valstr = "nil";
				end

				KarmaChatDebug("FI: " .. field .. " -> default: " .. valstr);
			else
				KarmaChatDebug("FI: " .. field .. " -> default: <table>");
			end
		end
	elseif ((initialvalue ~= nil) and (type(initialvalue) == "table") and KOH.TableIsEmpty(initialvalue)) then
		local backtrace = debugstack();
		local	iCount, bFound, i = #Stacks, false;
		for i = 1, iCount do
			if (Stacks[i] == backtrace) then
				bFound = true;
				break;
			end
		end
		if (not bFound) then
			tinsert(Stacks, backtrace);
			KarmaChatDebugFallbackSecondary("Empty table initializer (" .. tostring(iCount + 1) .. "): " .. backtrace);
		end
	end

	KarmaObj.ProfileStop("Karma_FieldInitialize");
end

function	Karma_FieldDeinitialize(dict, field, initialvalue, bSilent)
	KarmaObj.ProfileStart("Karma_FieldDeinitialize");

	if (dict == nil) or (field == nil) or (type(dict) ~= "table") then
		if (type(initialvalue) ~= "table") then
			KarmaChatDebugFallbackSecondary(KARMA_MSG_FIELDINIT_ERROR_VALUE .. initialvalue);
		else
			KarmaChatDebugFallbackSecondary(KARMA_MSG_FIELDINIT_ERROR_VALUE .. KARMA_MSG_FIELDINIT_ERROR_TABLE);
		end

		local backtrace = debugstack();
		KarmaChatDebugFallbackSecondary("CB: " .. backtrace);
	elseif (dict[field] == initialvalue) then
		dict[field] = nil;
		KarmaChatDebug("FI: " .. field .. " -> default: " .. tostring(initialvalue));
	end

	KarmaObj.ProfileStop("Karma_FieldDeinitialize");
end

----------------------------------------------
-- CONSTS
----------------------------------------------

local	KARMA_SUPPORTEDDATABASEVERSION = 14;

----------------------------------------------
-- LOCALS
----------------------------------------------

local	KDBC =
	{	-- empty initalizers so we keep track of what we use and how we name it...
		Realm = nil,				-- current realm name
		PlayerFaction = nil,			-- player faction
		PlayerName = nil,			-- player name

		CommonFactionObject = nil,		-- COMMON::FACTION object
		FactionObjectCache = nil,		-- SERVER::FACTION object
	};

----------------------------------------------
-- DB KEYS
----------------------------------------------

--------- TOP LEVEL --------------------------

-- TOP LEVEL FIELDS
local	KARMA_DB_L1 = {
			VERSION = "VERSION",
			COMMONLIST = "COMMON",
			REALMLIST = "REALMS",
			REALM_IDS = "REALM_IDS",		-- added in v13, reverse ID to realm info
			REALMGROUPS = "REALMGRPS",		-- added in v13, timestamp cross-server links

			XFACTIONHOLD = "XFACTIONHOLD",		-- moved to here in version 12

			RELOCATIONS = "RELOCATIONS",
		};

--------- COMMON LEVEL --------------------------

-- COMMON LEVEL FIELDS: -> DB_L2_C
local	KARMA_DB_L2_C = {
			REGIONNAMES = "REGIONNAMES",
			ZONENAMES = "ZONENAMES",
			FACTION = "FACTIONLIST",		-- unused after version 11
			LOCALE = "LOCALE_",				-- gets concatenated with current Locale
		};

-- COMMON/REGIONNAMES LEVEL FIELDS: DB_L3_CR
local	KARMA_DB_L3_CR = {
			ZONEIDS = "ZONEIDS",
			ISPVPZONE = "PVPZONE",
			ZONETYPE = "ZONETYPE"
		};

-- COMMON/LOCALE_* LEVEL FIELDS: TALENTS
local	KARMA_DB_L3_CL = {
			TALENTS = "TALENTS",
			REGIONS = "REGIONS",
		};

-- COMMON/FACTION LEVEL FIELDS: DB_L3_CF
local		KARMA_DB_L3_CF_QUESTNAMES = "QUESTNAMES";
local		KARMA_DB_L3_CF_QUESTINFOS = "QUESTINFOS";
local		KARMA_DB_L3_CF_RECENTLY_JOINED = "JOIN_MRU";	-- new in version 11
--------- REALM LEVEL --------------------------

-- REALM LEVEL FIELDS: DB_L3_RR
local		KARMA_DB_L3_RR_FACTION = "FACTIONLIST";

-- REALM/FACTION/<FACTION> LEVEL FIELDS: DB_L4_RRFF
local	KARMA_DB_L4_RRFF = {
			SERVER_ID = "SVR_ID",			-- added in v13

			CHARACTERLIST = "CHARACTERLIST",
			CHARACTER_IDS = "CHAR_IDS",		-- added in v13

			QUESTNAMES = "QUESTNAMES",
			ZONENAMES = "ZONENAMES",
			IGNORE24 = "IGNTMP",

			MEMBERLIST = "MEMBERLIST",		-- No longer used after version 3 of the database
			MEMBERBUCKETS = "MEMBERBUCKETS",
			ALTGROUPS = "ALTGROUPS",

			XFACTIONHOLD = "XFACTIONHOLD",		-- moved from here in version 12

			HISTORY_MOVED = "HIST_MOVED",
			HISTORY_SEEN = "HIST_SEEN",
		};

-- REALM/FACTION/<FACTION>/CHARACTERLIST OBJECT FIELDS: DB_L5_RRFFC
local	KARMA_DB_L5_RRFFC = {
			NAME = "NAME",
			GUID = "GUID",				-- added in v13 (server ID)
			KARMA_ID = "K_ID",			-- added in v13 (internal ID)
			XPTOTAL = "XPTOTAL",
			XPLAST = "XPLAST",
			XPMAX = "XPMAX",
			PLAYED = "PLAYED",
			PLAYEDLAST = "PLAYEDLAST",
			CONFIGPERCHAR = "CHARCONFIG",
			XPLVLSUM = "XPLVLSUM",
		};

-- REALM/FACTION/<FACTION>/IGNORE24 OBJECT FIELDS: DB_L5_RRFFI
local	KARMA_DB_L5_RRFFI = {
			GUID = "GUID",
			ACTIONS = "ACTIONS",
			IGNORE = "IGN",
			TIMEOUT = "TIMEOUT",
		};

-- Waawaa, "local" limit: 200
-- TODO: more KARMA_DB_??_*_* -> KARMA_DB_??_*.*

-- REALM/FACTION/<FACTION>/MEMBERLIST OBJECT FIELDS: DB_L5_RRFFM
local	KARMA_DB_L5_RRFFM = {
			LASTCHANGED_TIME = "TOUCH_TIME",
			LASTCHANGED_FIELD = "TOUCH_FIELD",
			LASTSEEN = "LASTSEEN",

			GUID = "GUID",
			NAME = "NAME",
			ALTGROUP = "ALTID",
			GUILD = "GUILD",
			LEVEL = "LEVEL",
			GENDER = "GENDER",
			RACE = "RACE",
			CLASS = "CLASS",
			CLASS_ID = "CLSID",
			ADDED_IN = "ADD_Z",

			RACE_EN = "RACEEN",
			CLASS_EN = "CLASSEN",
		};

-- #### --

local		KARMA_DB_L5_RRFFM_CONFLICT = "CONFLICT";

local		KARMA_DB_L5_RRFFM_TALENT = "TALENT";
local		KARMA_DB_L5_RRFFM_TALENTTREE = "TALENTTREE";
local		KARMA_DB_L5_RRFFM_KARMA = "KARMA";
local		KARMA_DB_L5_RRFFM_NOTES = "NOTES";
local		KARMA_DB_L5_RRFFM_PUBLIC_NOTES = "PUBLIC_NOTES";
-- stores imported total sum
local		KARMA_DB_L5_RRFFM_KARMA_IMPORTED = "K_IMP";
-- modifier for "time"-Karma: -1 = use default, 0 = off, 1 = on
local		KARMA_DB_L5_RRFFM_KARMA_TIME = "K_TIME";
-- old concept... dropped.
-- it's impossible to add "-3 underperforming" to "+3 nice" and get a sensible result...
-- must remain a while to clean out intermediate versions
local		KARMA_DB_L5_RRFFM_KARMA_MODSOC = "K_SOC";
local		KARMA_DB_L5_RRFFM_KARMA_MODSKILL = "K_SKILL";
-- TODO: three new values to filter on...
local		KARMA_DB_L5_RRFFM_SKILL = "SKILL";
local		KARMA_DB_L5_RRFFM_GEAR_PVP = "GEAR_PVP";
local		KARMA_DB_L5_RRFFM_GEAR_PVE = "GEAR_PVE";

local		KARMA_DB_L5_RRFFM_TIMESTAMP = "TIMESTAMP";
local		KARMA_DB_L5_RRFFM_TIMESTAMP_TRY = "TRY";
local		KARMA_DB_L5_RRFFM_TIMESTAMP_SUCCESS = "SUCCESS";

local		KARMA_DB_L5_RRFFM_JOINEDLAST_TIME = "JOINEDLAST";
local		KARMA_DB_L5_RRFFM_JOINEDLAST_CHAR = "JOINEDWITH";

local		KARMA_DB_L5_RRFFM_KARMA_TRUST = "K_TRUST";


-- REALM/FACTION/<FACTION>/MEMBERLIST/<Bucket>/<Member>/CHARACTERS/<Character> OBJECT FIELDS: DB_L6_RRFFMCC
-- character specific data
-- container
local		KARMA_DB_L5_RRFFM_CHARACTERS = "CHARACTERSPECIFIC";
local		KARMA_DB_L6_RRFFMCC_KARMA_ID = "K_IDREF";
-- data
local		KARMA_DB_L6_RRFFMCC_QUESTIDLIST = "QUESTIDLIST";
local		KARMA_DB_L6_RRFFMCC_QUESTEXLIST = "QUESTEXLIST";
local		KARMA_DB_L6_RRFFMCC_ZONEIDLIST = "ZONEIDLIST";
local		KARMA_DB_L6_RRFFMCC_XP = "XP";
local		KARMA_DB_L6_RRFFMCC_XPLAST = "XPLAST";
local		KARMA_DB_L6_RRFFMCC_XPMAX = "XPMAX";
local		KARMA_DB_L6_RRFFMCC_XPLVL = "XPLVL";
local		KARMA_DB_L6_RRFFMCC_PLAYED = "PLAYED";
local		KARMA_DB_L6_RRFFMCC_PLAYEDPVP = "PLAYEDPVP";
local		KARMA_DB_L6_RRFFMCC_PLAYEDLAST = "PLAYEDLAST";
local		KARMA_DB_L6_RRFFMCC_JOINEDLAST = "JOINEDLAST";

local		KARMA_DB_L6_RRFFMCC_ACHIEVED = "ACHIEVED";

-- ../CHARACTERS/<Character>/REGIONLIST OBJECT FIELDS: DB_L7_RRFFMCCR
-- region tracking: which when how long...
-- container
local		KARMA_DB_L6_RRFFMCC_REGIONLIST = "REG_L";			-- list of
-- data
-- KARMA_DB_L7_RRFFMCCRR_ -> KARMA_DB_L7_RRFFMCCRR_
local		KARMA_DB_L7_RRFFMCCRR_KEY = "RL_REGKEY";				-- an ID (RegionID + Difficulty!)
local		KARMA_DB_L7_RRFFMCCRR_ID = "RL_REGID";					-- a RegionID
local		KARMA_DB_L7_RRFFMCCRR_DIFF = "RL_REGDIFF";				-- a difficulty
local		KARMA_DB_L7_RRFFMCCRR_PLAYEDTOTAL = "REG_TOTAL";		-- summed time
-- another container
local		KARMA_DB_L7_RRFFMCCRR_PLAYEDDAYS = "REG_DAYS";			-- list of
-- and another dataset
local		KARMA_DB_L8_RRFFMCCRRD_KEY = "REG_DAYKEY";					-- an ID
local		KARMA_DB_L8_RRFFMCCRRD_START = "RD_FROM";					-- start (datetime)
local		KARMA_DB_L8_RRFFMCCRRD_END = "RD_TILL";						-- end (datetime)

-- REALM/FACTION/<FACTION>/XFACTIONHOLD OBJECT FIELDS: additional field besides DB_L5_RRFFM
local		KARMA_DB_L5_RRFFX_SOURCE = "SOURCE";
local		KARMA_DB_L5_RRFFX_FACTION = "FACTION";

local		KARMA_ALPHACHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

--
--	Legacy broken version
--
local	function	Karma_AccentedToPlain(thechar)
	if (strlen(thechar) == 1) then
		KarmaChatDebug("FIXME: AccentedToPlain only got initial ~ " .. debugstack());
	end

	thechar = strsub(thechar, 1, 1);

	local	asccode = string.byte(thechar);
	-- if (asccode < 127) then
	-- A..Z => return same
	if (asccode >= string.byte("A")) and (asccode <= string.byte("Z")) then
		return thechar;
	end

	-- a..z => return A..Z - version
	if (asccode >= string.byte("a")) and (asccode <= string.byte("z")) then
		return string.upper(thechar);
	end

	-- replace via table - broken variant :D
	local index, value, i;
	for index, value in pairs(KARMA_ALPHACHARS_ACCENT) do
		for i = 1, getn(value) do
			if value[i] == asccode then
				return index;
			end
		end
	end

	-- now, this might be *anything*... better throw it into a bucket we know exists
	--return thechar;
	-- choose a bucket, that is probably rarely used, but *does* exist:
	return "Y";
end

--
--- teh new order
--

local	function	DB_Server_ID_Init(oRealms, oRealmIDs, sRealm)
	local	oServerFaction = oRealms[sRealm];
	local	iServerID = oServerFaction[KARMA_DB_L4_RRFF.SERVER_ID];
	if (iServerID == nil) then
		local	i, sServer, oServer = 0;
		for sServer, oServer in pairs(oRealms) do
			i = math.max(i, oServer[KARMA_DB_L4_RRFF.SERVER_ID] or 0);
		end

		iServerID = i + 1;
		oServerFaction[KARMA_DB_L4_RRFF.SERVER_ID] = iServerID;
		oRealmIDs[iServerID] = sRealm;
	end

	return iServerID;
end

local	function	DB_Char_ID_Init(iServerID, oCharacters, oCharIDs, sCharacter)
	iServerID = bit.lshift(iServerID, 16);
	local	oCharacter = oCharacters[sCharacter];
	local	iCharID = oCharacter[KARMA_DB_L5_RRFFC.KARMA_ID];
	if (iCharID == nil) then
		local	i, sChar, oChar = iServerID;
		for sChar, oChar in pairs(oCharacters) do
			i = math.max(i, oChar[KARMA_DB_L5_RRFFC.KARMA_ID] or iServerID);
		end

		oCharacter[KARMA_DB_L5_RRFFC.KARMA_ID] = i + 1;
		oCharIDs[i + 1] = sCharacter;
	end
end

function	KarmaObj.DB.ServerExists(sRealm)
	return (type(sRealm) == "string") and (type(KDBC.Faction[KARMA_DB_L1.REALMGROUPS][sRealm]) == "table");
end

function	KarmaObj.DB.ServerCreate(oFactionDB, sRealm, bPartial)
	if ((type(oFactionDB) ~= "table") or (type(sRealm) ~= "string")) then
		KarmaChatDebug("Improper args to ServerCreate: " .. tostring(oFactionDB) .. " / " .. tostring(sRealm));
		return
	end
	if (strlen(sRealm) < 5) then
		KarmaChatDebug("Improper arg to ServerCreate: <" .. sRealm .. ">");
		return
	end

--	<FACTIONDB> = {};
--		REALMS = {};
--			<REALMNAME> = {};

	-- <FACTIONDB>/REALMS
	KOH.TableInit(oFactionDB, KARMA_DB_L1.REALMGROUPS);

	if (type(oFactionDB[KARMA_DB_L1.REALMGROUPS][sRealm]) ~= "table") then
		KarmaChatSecondary("Adding new container for server <" .. sRealm .. ">...");
	end

	KOH.TableInit(oFactionDB[KARMA_DB_L1.REALMGROUPS], sRealm);

	-- <FACTIONDB>/REALMS["realmName"]
	KOH.TableInit(oFactionDB, KARMA_DB_L1.REALMLIST);
	KOH.TableInit(oFactionDB[KARMA_DB_L1.REALMLIST], sRealm);

	local	oServerFaction = oFactionDB[KARMA_DB_L1.REALMLIST][sRealm];
	KOH.TableInit(oFactionDB, KARMA_DB_L1.REALM_IDS);
	local	iServerID = DB_Server_ID_Init(oFactionDB[KARMA_DB_L1.REALMLIST], oFactionDB[KARMA_DB_L1.REALM_IDS], sRealm);

	------------------------------------------------------------------------

	--	ROOT/REALMS["realmName"]/FACTIONS["factionname]/MEMBERBUCKETS
	KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.MEMBERBUCKETS);

	local	buckets, i = {};
	for i = 1, strlen(KARMA_ALPHACHARS) do
		local	sBucketName = strsub(KARMA_ALPHACHARS, i, i);
		KOH.TableInit(oServerFaction[KARMA_DB_L4_RRFF.MEMBERBUCKETS], sBucketName);
	end

	------------------------------------------------------------------------

	--	ROOT/REALMS["realmName"]/FACTIONS["factionname]/ALTGROUPS
	KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.ALTGROUPS);

	------------------------------------------------------------------------

	if (not bPartial) then
		KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.CHARACTERLIST);
		KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.CHARACTER_IDS);
	--	KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.QUESTNAMES);
	--	KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.ZONENAMES);

		KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.IGNORE24);
		KarmaObj.DB.I24.Clean(oServerFaction);
	end

	return iServerID, oServerFaction;
end

function	KarmaObj.DB.Create()
	local	sRealm = GetCVar("realmName");
	local	sPlayerFaction = UnitFactionGroup("player");
	if ((sRealm == nil) or (sPlayerFaction == nil)) then
		local	s = "";
		if (sRealm == nil) then
			s = s .. "/realm name";
		end
		if (sPlayerFaction == nil) then
			s = s .. "/character faction";
		end

		DEFAULT_CHAT_FRAME:AddMessage("Karma: |cFFFF8080Fatal initialization error! Couldn't determine your " .. strsub(s, 2) .. "!! DB remains uninitialized.|r");

		return -1
	end

	local	oFactionDB = _G["KarmaAvEnK" .. sPlayerFaction];
	if (type(oFactionDB) ~= "table") then
		DEFAULT_CHAT_FRAME:AddMessage("Karma: |cFFFF8080Fatal initialization error! Couldn't initialize the load-on demand module for your faction (" .. sPlayerFaction .. ")!! DB remains uninitialized.|r (Make sure the module KarmaDB" .. sPlayerFaction .. " is enabled.)");

		return -1
	end

	local	sPlayername = UnitName("player");

	KDBC.Faction = oFactionDB;
	KDBC.Realm = sRealm;
	KDBC.PlayerFaction = sPlayerFaction;
	KDBC.PlayerName = sPlayername;
	KDBC.Locale = GetLocale();

	-- This creates fields in the database as we need them. Thus it is perfectly
	-- safe to call this function over and over again, as it only adds fields as they
	-- are required.
	-- At the end of this function, we will have an empty skeleton database setup.

	------------------------------------------------------------------------
	------------------------------------------------------------------------
	------------------------------------------------------------------------

	--	ROOT
	if (type(KarmaData) ~= "table") then
		KarmaData = {};
	end
	Karma_FieldInitialize(KarmaData, KARMA_DB_L1.VERSION, KARMA_SUPPORTEDDATABASEVERSION);

	------------------------------------------------------------------------
	------------------------------------------------------------------------
	------------------------------------------------------------------------

	-- Questlists, Zonelists, and Subzonelists NEED NOT be per server!

	--	ROOT/COMMON
	KOH.TableInit(KarmaData, KARMA_DB_L1.COMMONLIST);
	local	oGlobalCommon = KarmaData[KARMA_DB_L1.COMMONLIST];
	KOH.TableInit(oFactionDB, KARMA_DB_L1.COMMONLIST);
	local	oFactionCommon = oFactionDB[KARMA_DB_L1.COMMONLIST];

	--	ROOT/COMMON/REGIONNAMES
	KOH.TableInit(oGlobalCommon, KARMA_DB_L2_C.REGIONNAMES);
	local	CommonZoneList = oGlobalCommon[KARMA_DB_L2_C.REGIONNAMES];
	for k, v in pairs(CommonZoneList) do
		if (type(v) ~= "table") then
			KOH.TableInit(CommonZoneList, k);
			KOH.TableInit(CommonZoneList[k], KARMA_DB_L3_CR.ZONEIDS);
		end
	end


	--	ROOT/COMMON/ZONENAMES
	KOH.TableInit(oGlobalCommon, KARMA_DB_L2_C.ZONENAMES);

	-- ROOT/COMMON: /LOCALE_*/TALENTS
	local	sLocale = KARMA_DB_L2_C.LOCALE .. KDBC.Locale;
	KOH.TableInit(oGlobalCommon, sLocale);
	local	oGlobalCommonLocale = oGlobalCommon[sLocale];
	KOH.TableInit(oGlobalCommonLocale, KARMA_DB_L3_CL.TALENTS);

	------------------------------------------------------------------------
--[[
--	ROOT = {};
--		COMMON = {};
--			FACTIONLIST = {};
--				<Faction> = {};
--					QUESTNAMES = {};

	--	ROOT/COMMON/FACTIONLIST
	KOH.TableInit(oGlobalCommon, KARMA_DB_L2_C.FACTION);


	--	ROOT/COMMON/FACTIONLIST[<Faction>]
	local	oCommonFactionList = oGlobalCommon[KARMA_DB_L2_C.FACTION];
	KOH.TableInit(oCommonFactionList, sPlayerFaction);


	--	ROOT/COMMON/FACTIONLIST[<Faction>]/QUESTNAMES
	local	oGlobalCommonFaction = oCommonFactionList[sPlayerFaction];
	KOH.TableInit(oGlobalCommonFaction, KARMA_DB_L3_CF_QUESTNAMES);
	KOH.TableInit(oGlobalCommonFaction, KARMA_DB_L3_CF_QUESTINFOS);
	KDBC.CommonFactionObject = oGlobalCommonFaction;
]]--
	------------------------------------------------------------------------

--	<FACTIONDB> = {};
--		COMMON = {};
--			QUESTNAMES = {};
--			QUESTINFOS = {};
	KOH.TableInit(oFactionCommon, KARMA_DB_L3_CF_QUESTNAMES);
	KOH.TableInit(oFactionCommon, KARMA_DB_L3_CF_QUESTINFOS);
	KOH.TableInit(oFactionCommon, KARMA_DB_L3_CF_RECENTLY_JOINED);
	KOH.TableInit(oFactionCommon[KARMA_DB_L3_CF_RECENTLY_JOINED], sRealm);

	KDBC.CommonFactionObject = oFactionCommon;

	------------------------------------------------------------------------
	------------------------------------------------------------------------
	------------------------------------------------------------------------

	-- relocation records: store if and which data is stored in LOD modules
	-- ROOT/RELOCATIONS
	KOH.TableInit(KarmaData, KARMA_DB_L1.RELOCATIONS);

	-- ROOT/XFACTIONHOLD (moved up in v12 to avoid loading XFaction DBs)
	KOH.TableInit(KarmaData, KARMA_DB_L1.XFACTIONHOLD);

	------------------------------------------------------------------------
	local	iServerID, oServerFaction = KarmaObj.DB.ServerCreate(oFactionDB, sRealm);
	------------------------------------------------------------------------

	--	ROOT/REALMS["realmName"]/FACTIONS["factionname]/CHARACTERLIST
	local	oCharacters = oServerFaction[KARMA_DB_L4_RRFF.CHARACTERLIST];
	KOH.TableInit(oCharacters, sPlayername);
	local	oCharIDs = oServerFaction[KARMA_DB_L4_RRFF.CHARACTER_IDS];
	DB_Char_ID_Init(iServerID, oCharacters, oCharIDs, sPlayername);


	--	ROOT/REALMS["realmName"]/FACTIONS["factionname]/CHARACTERLIST["player"]
	local	oCharacter = oCharacters[sPlayername];

	Karma_FieldInitialize(oCharacter, KARMA_DB_L5_RRFFC.NAME, sPlayername);
	Karma_FieldInitialize(oCharacter, KARMA_DB_L5_RRFFC.GUID, UnitGUID("player"));
	Karma_FieldInitialize(oCharacter, KARMA_DB_L5_RRFFC.XPTOTAL, 0);
	Karma_FieldInitialize(oCharacter, KARMA_DB_L5_RRFFC.XPLAST, UnitXP("player"));
	Karma_FieldInitialize(oCharacter, KARMA_DB_L5_RRFFC.XPMAX, UnitXPMax("player"));
	Karma_FieldInitialize(oCharacter, KARMA_DB_L5_RRFFC.XPLVLSUM, 0);

	KOH.TableInit(oCharacter, KARMA_DB_L5_RRFFC.CONFIGPERCHAR);

	------------------------------------------------------------------------

	KDBC.FactionObjectCache = nil;

	------------------------------------------------------------------------

	--	ROOT/REALMS["realmName"]/FACTIONS["factionname]/HISTORY
	-- this is a list of { ["name"] = timestamp } and describes old entries which were moved out to KarmaTrans/KarmaHistory
	KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.HISTORY_MOVED);
	-- this is a list of { ["name"] = timestamp } and describes entries which should be pulled back as the player is back
	KOH.TableInit(oServerFaction, KARMA_DB_L4_RRFF.HISTORY_SEEN);

	return 1;
end

function	KarmaObj.DB.Upgrade()
--DEFAULT_CHAT_FRAME:AddMessage("DBU: " .. KarmaData[KARMA_DB_L1.VERSION] .. " -> " .. KARMA_SUPPORTEDDATABASEVERSION);
	if (KarmaData[KARMA_DB_L1.VERSION] == KARMA_SUPPORTEDDATABASEVERSION) then
--DEFAULT_CHAT_FRAME:AddMessage("DBU: nothing to do.");
		return
	end

	if (KarmaData[KARMA_DB_L1.VERSION] > KARMA_SUPPORTEDDATABASEVERSION) then
		DEFAULT_CHAT_FRAME:AddMessage("Karma: |cFFFF8080You downgraded Karma to a previous version. Will NOT touch database with unknown changes in format.|r");
		return -1
	end

--DEFAULT_CHAT_FRAME:AddMessage("DBU: precheck of DB...");

	local	bLoadOk = true;
	if (not IsAddOnLoaded("KarmaDBAlliance")) then
		local	iLoaded, sWhyNot = LoadAddOn("KarmaDBAlliance");
		if (not iLoaded) then
			bLoadOk = false;
			KarmaChatDebug("DB Alliance: " .. sWhyNot);
		end
	end
	if (not IsAddOnLoaded("KarmaDBHorde")) then
		local	iLoaded, sWhyNot = LoadAddOn("KarmaDBHorde");
		if (not iLoaded) then
			bLoadOk = false;
			KarmaChatDebug("DB Horde: " .. sWhyNot);
		end
	end

	if (not bLoadOk or (type(KarmaAvEnKAlliance) ~= "table") or (type(KarmaAvEnKHorde) ~= "table")) then
		DEFAULT_CHAT_FRAME:AddMessage("Karma: |cFFFF8080Update of database required, but could not load the load-on-demand modules for both factions successfully.|r");
		return -1
	end

	if (KarmaData[KARMA_DB_L1.VERSION] <= 10) then
		local	realmlist, realmname, realmdata;
		local	factionlist, factionname, factiondata;

		-- check for missing outer tables outside the loop:
		realmlist = KarmaData[KARMA_DB_L1.REALMLIST];
		if (type(realmlist) ~= "table") then
			-- something has to go wrong in a very weird way for this
			return -1
		end

		for realmname, realmdata in pairs(realmlist) do
			if (type(realmdata) ~= "table") then
				-- Hm. Giving up.
				return -1
			end

			factionlist = realmdata[KARMA_DB_L3_RR_FACTION];
			if (type(factionlist) ~= "table") then
				if (factionlist ~= nil) then
					-- Hm. Giving up.
					return -1
				else
					-- create the missing table
					realmdata[KARMA_DB_L3_RR_FACTION] = {};
					factionlist = realmdata[KARMA_DB_L3_RR_FACTION];
				end
			end

			for factionname, factiondata in pairs(factionlist) do
				if (type(factiondata) ~= "table") then
					-- Hm. Giving up.
					return -1
				end
			end
		end
	end

--DEFAULT_CHAT_FRAME:AddMessage("DBU: upgrade in progress...");

	-- force reinit, if it already happened
	KDBC.FactionObjectCache = nil;

	-- Loop until the database has been upgraded all the iterations of the formats between.
	-- (it actually is NOT a loop, as every next version upgrade is following after the previous in the first pass)
	if (KarmaData[KARMA_DB_L1.VERSION] ~= KARMA_SUPPORTEDDATABASEVERSION) then
		local	iVersion = KarmaData[KARMA_DB_L1.VERSION] or 2;
		KarmaChatDefault("Upgrading DB version from " .. iVersion .. " to " .. (iVersion + 1) .. "...");

		---
		---	Upgrade from version 2 --> version 3
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 2) then
			local	realmlist, realmname, realmdata = KarmaData[KARMA_DB_L1.REALMLIST];
			for realmname, realmdata in pairs(realmlist) do
				-- Update to the version 3 database.
				-- Changes are only mandatory field additions to the
				--	Characterlist entries
				--	Memberlist Entries
				local	factionlist, factionname, factiondata = realmdata[KARMA_DB_L3_RR_FACTION];
				for factionname, factiondata in pairs(factionlist) do
					local	ckey, cvalue;
					for ckey, cvalue in pairs(factiondata[KARMA_DB_L4_RRFF.CHARACTERLIST]) do
						if (cvalue ~= nil) then
							cvalue[KARMA_DB_L5_RRFFC.PLAYED] = 0;
							cvalue[KARMA_DB_L5_RRFFC.PLAYEDLAST] = 0;
						end
					end

					local	mkey, mvalue;
					for mkey, mvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERLIST]) do
						if (mvalue ~= nil) then
							KOH.TableInit(mvalue, KARMA_DB_L5_RRFFM_CHARACTERS);
							local	key, value;
							for key, value in pairs(mvalue[KARMA_DB_L5_RRFFM_CHARACTERS]) do
								if (value ~= nil) then
									cspecificobject = value;
									cspecificobject[KARMA_DB_L6_RRFFMCC_PLAYED] = 0;
									cspecificobject[KARMA_DB_L6_RRFFMCC_PLAYEDLAST] = 0;
								end
							end
						end
					end
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 3;
		end
		---
		---	End upgrade version 2 --> version 3
		---

		---
		---	Upgrade from version 3 --> version 4
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 3) then
			local	realmlist, realmname, realmdata = KarmaData[KARMA_DB_L1.REALMLIST];
			for realmname, realmdata in pairs(realmlist) do
				-- put lMembers into buckets for quicker sorting, and accessing.
				local	factionlist, factionname, factiondata = realmdata[KARMA_DB_L3_RR_FACTION];
				for factionname, factiondata in pairs(factionlist) do
					local	buckets = {};
					local	i = 0;
					local	sBucketName;
					for i = 1, strlen(KARMA_ALPHACHARS) do
						sBucketName = strsub(KARMA_ALPHACHARS, i, i);
						buckets[sBucketName] = {};
					end

					factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS] = buckets;

					local	mkey, mvalue;
					for mkey, mvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERLIST]) do
						if (mvalue ~= nil) then
							sBucketName = Karma_AccentedToPlain(mkey);
							factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS][sBucketName][mkey] = mvalue;
						end
					end

					factiondata[KARMA_DB_L4_RRFF.MEMBERLIST] = nil; -- no longer used get rid of the extra data
				end
			end
			KarmaData[KARMA_DB_L1.VERSION] = 4;
		end
		---
		---	End upgrade version 3 --> version 4;
		---

		local	realmlist, realmname, realmdata;
		local	factionlist, factionname, factiondata;
		local	bkey, bvalue, ckey, cvalue, mkey, mvalue, key, value;
		local	cspecificobject;

		---
		---	Upgrade from version 4 --> version 5
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 4) then
			realmlist = KarmaData[KARMA_DB_L1.REALMLIST];
			for realmname, realmdata in pairs(realmlist) do
				-- add container for zone tracking
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION];
				for factionname, factiondata in pairs(factionlist) do
					if (factionname ~= nil) then
						for bkey, bvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
							for mkey, mvalue in pairs(bvalue) do
								if (mvalue~= nil) then
									for ckey, cvalue in pairs(mvalue[KARMA_DB_L5_RRFFM_CHARACTERS]) do
										cvalue[KARMA_DB_L6_RRFFMCC_ZONEIDLIST] = {};
									end
								end
							end
						end
					end
				end
			end
			KarmaData[KARMA_DB_L1.VERSION] = 5;
		end
		---
		---	End upgrade version 4 --> version 5;
		---

		---
		---	Upgrade from version 5 --> version 6
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 5) then
			realmlist = KarmaData[KARMA_DB_L1.REALMLIST] or {};
			for realmname, realmdata in pairs(realmlist) do
				-- put lMembers into buckets for quicker sorting, and accessing.
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION] or {};
				for factionname, factiondata in pairs(factionlist) do
					if (factionname ~= nil) then
						for bkey, bvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
							for mkey, mvalue in pairs(bvalue) do
								if (mvalue~= nil) then
									for ckey, cvalue in pairs(factiondata[KARMA_DB_L4_RRFF.CHARACTERLIST]) do
										if (ckey~= nil and ckey~= "") then
											Karma_MemberList_Add(mkey, factiondata);
										end
									end
								end
							end
						end
					end
				end
			end
			KarmaData[KARMA_DB_L1.VERSION] = 6;
		end
		---
		---	End upgrade version 5 --> version 6;
		---

		---
		---	Upgrade from version 6 --> version 7
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 6) then
			realmlist = KarmaData[KARMA_DB_L1.REALMLIST] or {};
			for realmname, realmdata in pairs(realmlist) do
				-- put lMembers into buckets for quicker sorting, and accessing.
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION] or {};
				for factionname, factiondata in pairs(factionlist) do
					if (factionname ~= nil) then
						for bkey, bvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
							for mkey, mvalue in pairs(bvalue) do
								if (mvalue~= nil) then
									mvalue[KARMA_DB_L5_RRFFM.GENDER] = -1;
								end
							end
						end
					end
				end
			end
			KarmaData[KARMA_DB_L1.VERSION] = 7;
		end
		---
		---	End upgrade version 6 --> version 7;
		---

		---
		---	Upgrade from version 7 --> version 8
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 7) then
			realmlist = KarmaData[KARMA_DB_L1.REALMLIST];
			for realmname, realmdata in pairs(realmlist) do
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION];
				for factionname, factiondata in pairs(factionlist) do
					if (factionname ~= nil) then
						-- move zonelists and questlists to global
						local	factionzonelist = factiondata[KARMA_DB_L4_RRFF.ZONENAMES];
						local	zonetranslationlist = {};
						local	zkey, zvalue;
						for zkey, zvalue in pairs(factionzonelist) do
							zonetranslationlist[zkey] = CommonRegionZoneAdd(nil, zvalue, nil, nil);
						end

						local	factionquestlist = factiondata[KARMA_DB_L4_RRFF.QUESTNAMES];
						local	questtranslationlist = {};
						local	qkey, qvalue;
						for qkey, qvalue in pairs(factionquestlist) do
							local	qix = KarmaObj.DB.CF.QuestListAdd(qvalue, factionname);
							if (qix) then
								questtranslationlist[qkey] = qix;
							else
								return -1;
							end
						end

						local	bkey, bvalue;
						for bkey, bvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
							for mkey, mvalue in pairs(bvalue) do
								if (mvalue~= nil) then
									local charspecificlist = mvalue[KARMA_DB_L5_RRFFM_CHARACTERS];
									for ckey, cvalue in pairs(charspecificlist) do
										local charzonelist = cvalue[KARMA_DB_L6_RRFFMCC_ZONEIDLIST];
										for zkey, zvalue in pairs(charzonelist) do
											if (zvalue > 0) then
												local tmpzvalue = zonetranslationlist[zvalue];
												local newzvalue;
												if (tmpzvalue == nil) then
													KarmaChatDebug(mkey.."::"..ckey..": ["..zkey.."] = "..zvalue.." => nil!");
													newzvalue = 0;
												else
													newzvalue = -tmpzvalue;
												end
												charzonelist[zkey] = newzvalue;
											end
										end

										local charquestlist = cvalue[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
										for qkey, qvalue in pairs(charquestlist) do
											if (qvalue > 0) then
												local tmpqvalue = questtranslationlist[qvalue];
												local newqvalue;
 												if (tmpqvalue == nil) then
 													KarmaChatDebug(mkey.."::"..ckey..": ["..qkey.."] = "..qvalue.." => nil!");
													newqvalue = 0;
												else
													newqvalue = -tmpqvalue;
 												end
												charquestlist[qkey] = newqvalue;
											end
										end
									end
								end
							end
						end
					end
				end
			end

			for realmname, realmdata in pairs(realmlist) do
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION] or {};
				for factionname, factiondata in pairs(factionlist) do
					if (factionname ~= nil) then
						-- remove local zonelists and questlists
						factiondata[KARMA_DB_L4_RRFF.QUESTNAMES] = nil;
						factiondata[KARMA_DB_L4_RRFF.ZONENAMES] = nil;
					end
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 8;
		end
		---
		---	End upgrade version 7 --> version 8
		---

		---
		---	Upgrade from version 8 --> version 9
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 8) then
			realmlist = KarmaData[KARMA_DB_L1.REALMLIST] or {};
			for realmname, realmdata in pairs(realmlist) do
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION] or {};
				for factionname, factiondata in pairs(factionlist) do
					if (factionname ~= nil) then
						for bkey, bvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
							for mkey, mvalue in pairs(bvalue) do
								if (mvalue~= nil) then
									local charspecificlist = mvalue[KARMA_DB_L5_RRFFM_CHARACTERS];
									for ckey, cvalue in pairs(charspecificlist) do
										local charquestexlist = cvalue[KARMA_DB_L6_RRFFMCC_QUESTEXLIST];
										if (type(charquestexlist) == "table") then
											for qid, qexl in pairs(charquestexlist) do
												local qexlnew = {};
												for qkey, qvalue in pairs(qexl) do
													if (type(qkey) == "number") then
														qexlnew["O." .. qkey] = qvalue;
													else
														qexlnew[qkey] = qvalue;
													end
												end

												charquestexlist[qid] = qexlnew;
											end
										end
									end
								end
							end
						end
					end
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 9;
		end
		---
		---	End upgrade version 8 --> version 9
		---

		---
		---	Upgrade from version 9 --> version 10
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 9) then
			realmlist = KarmaData[KARMA_DB_L1.REALMLIST] or {};
			for realmname, realmdata in pairs(realmlist) do
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION] or {};

				local sBucketOld, sBucketNew;
				for factionname, factiondata in pairs(factionlist) do
					for bkey, bvalue in pairs(factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
						for mkey, mvalue in pairs(bvalue) do
							sBucketOld = Karma_AccentedToPlain(mkey);
							sBucketNew = KarmaObj.NameToBucket(mkey);
							if (sBucketOld ~= sBucketNew) then
								factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS][sBucketNew][mkey] = mvalue;
								factiondata[KARMA_DB_L4_RRFF.MEMBERBUCKETS][sBucketOld][mkey] = nil;
								KarmaChatDebug("DBUpdate(9->10): Moved <" .. mkey .. "> from Bucket " .. sBucketOld .. " to " .. sBucketNew .. ".");
							end
						end
					end
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 10;
		end
		---
		---	End upgrade version 9 --> version 10
		---

		---
		---	Upgrade from version 10 --> version 11
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 10) then
			local	oCommonFactionList, sFaction, oFactionGlobalDB = KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.FACTION];
			for sFaction, oFactionGlobalDB in pairs(oCommonFactionList) do
				local	oFactionLocalDB = _G["KarmaAvEnK" .. sFaction][KARMA_DB_L1.COMMONLIST];
				if (type(oFactionLocalDB) ~= "table") then
					_G["KarmaAvEnK" .. sFaction][KARMA_DB_L1.COMMONLIST] = {};
					oFactionLocalDB = _G["KarmaAvEnK" .. sFaction][KARMA_DB_L1.COMMONLIST];
				end

				local	oFields, iQuest, sQuest = { KARMA_DB_L3_CF_QUESTNAMES, KARMA_DB_L3_CF_QUESTINFOS };
				for iQuest, sQuest in pairs(oFields) do
					if ((type(oFactionGlobalDB[sQuest]) == "table") and not KOH.TableIsEmpty(oFactionGlobalDB[sQuest])) then
						oFactionLocalDB[sQuest] = oFactionGlobalDB[sQuest];
					end
					oFactionGlobalDB[sQuest] = nil;
				end
			end
			KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.FACTION] = nil;

			KDBC.CommonFactionObject = oFactionCommon;

			KOH.TableInit(KarmaAvEnKAlliance, KARMA_DB_L1.REALMLIST);
			KOH.TableInit(KarmaAvEnKHorde, KARMA_DB_L1.REALMLIST);

			realmlist = KarmaData[KARMA_DB_L1.REALMLIST] or {};
			for realmname, realmdata in pairs(realmlist) do
				factionlist = realmdata[KARMA_DB_L3_RR_FACTION] or {};
				for factionname, factiondata in pairs(factionlist) do
					local	oFactionDB = _G["KarmaAvEnK" .. factionname];
					oFactionDB[KARMA_DB_L1.REALMLIST][realmname] = factiondata;
				end
			end
			KarmaData[KARMA_DB_L1.REALMLIST] = nil;

			KarmaData[KARMA_DB_L1.VERSION] = 11;
		end
		---
		---	End upgrade version 10 --> version 11
		---

		---
		---	Upgrade from version 11 --> version 12
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 11) then
			local	oFactionDBs, sFaction, oFactionDB = { ["Alliance"] = KarmaAvEnKAlliance, ["Horde"] = KarmaAvEnKHorde };
			for sFaction, oFactionDB in pairs(oFactionDBs) do
				realmlist = oFactionDB[KARMA_DB_L1.REALMLIST] or {};
				for realmname, realmdata in pairs(realmlist) do
					if realmdata[KARMA_DB_L4_RRFF.XFACTIONHOLD] then
						local	oXFactionHold = KarmaData[KARMA_DB_L1.XFACTIONHOLD];
						KOH.TableInit(oXFactionHold, realmname);
						oXFactionHold[sFaction] = realmdata[KARMA_DB_L4_RRFF.XFACTIONHOLD];
					end
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 12;
		end
		---
		---	End upgrade version 11 --> version 12
		---

		---
		---	Upgrade from version 12 --> version 13
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 12) then
			local	oFactionDBs, sFaction, oFactionDB = { ["Alliance"] = KarmaAvEnKAlliance, ["Horde"] = KarmaAvEnKHorde };
			for sFaction, oFactionDB in pairs(oFactionDBs) do
				realmlist = oFactionDB[KARMA_DB_L1.REALMLIST];
				if (type(realmlist) == "table") then
					KOH.TableInit(oFactionDB, KARMA_DB_L1.REALM_IDS);
					local	oRealmIDs = oFactionDB[KARMA_DB_L1.REALM_IDS];

					-- initialize all the IDs we require
					for realmname, realmdata in pairs(realmlist) do
						local	iServerID = DB_Server_ID_Init(realmlist, oRealmIDs, realmname);

						local	oCharacters = realmdata[KARMA_DB_L4_RRFF.CHARACTERLIST];
						if (type(oCharacters) == "table") then
							KOH.TableInit(realmdata, KARMA_DB_L4_RRFF.CHARACTER_IDS);
							local	oCharIDs = realmdata[KARMA_DB_L4_RRFF.CHARACTER_IDS];
							for ckey, cvalue in pairs(oCharacters) do
								DB_Char_ID_Init(iServerID, oCharacters, oCharIDs, ckey);
							end
						end
					end
				else
					realmlist = {};
				end

				-- insert the IDs in all the right places
				for realmname, realmdata in pairs(realmlist) do
					local	iServerID = realmdata[KARMA_DB_L4_RRFF.SERVER_ID];
					for bkey, bvalue in pairs(realmdata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
						for mkey, mvalue in pairs(bvalue) do
							local oCharsOld = mvalue[KARMA_DB_L5_RRFFM_CHARACTERS];
							if (type(oCharsOld) == "table") then
								local	oCharsNew = {};
								for ckey, cvalue in pairs(oCharsOld) do
									local	sCombined = ckey .. '@' .. realmname;
									local	iID = KarmaObj.DB.MC.NameToID(sCombined, oFactionDB);
									if (iID == nil) then
										KarmaChatDebugStack("Failed to resolve [" .. sCombined .. "]", nil, true);
										return -1;
									end

									cvalue[KARMA_DB_L6_RRFFMCC_KARMA_ID] = iID;
									tinsert(oCharsNew, cvalue);
								end

								mvalue[KARMA_DB_L5_RRFFM_CHARACTERS] = oCharsNew;
							end
						end
					end
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 13;
		end
		---
		---	End upgrade version 12 --> version 13
		---

		---
		---	Upgrade from version 13 --> version 14
		---
		if (KarmaData[KARMA_DB_L1.VERSION] == 13) then
			local	oFactionDBs, sFaction, oFactionDB = { ["Alliance"] = KarmaAvEnKAlliance, ["Horde"] = KarmaAvEnKHorde };
			for sFaction, oFactionDB in pairs(oFactionDBs) do
				realmlist = oFactionDB[KARMA_DB_L1.REALMLIST] or {};

				local	oServers = {};
				local	oMovables = {};

				for realmname, realmdata in pairs(realmlist) do
					for bkey, bvalue in pairs(realmdata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
						for mkey, mvalue in pairs(bvalue) do
							if (strfind(mvalue[KARMA_DB_L5_RRFFM.NAME], '@', 1, true)) then
								local	sName, sServer = strsplit('@', mvalue[KARMA_DB_L5_RRFFM.NAME]);
								oServers[sServer] = true;
								mvalue[KARMA_DB_L5_RRFFM.NAME] = sName;
								tinsert(oMovables, { Bucket = bkey, Name = sName, Server = sServer, Data = mvalue } );
								bvalue[mkey] = nil;
							end
						end
					end
				end

				local	iCnt, i = #oMovables;
				for realmname, realmdata in pairs(oServers) do
					local	_, oServerFaction = KarmaObj.DB.ServerCreate(oFactionDB, realmname, true);
					local	oBuckets = oServerFaction[KARMA_DB_L4_RRFF.MEMBERBUCKETS];

					local	iMoveCnt = 0;
					local	iMergeCnt = 0;
					for i = 1, iCnt do
						if (oMovables[i].Server == realmname) then
							local	oMove = oMovables[i];
							if (oBuckets[oMove.Bucket][oMove.Name] == nil) then
								iMoveCnt = iMoveCnt + 1;
								oBuckets[oMove.Bucket][oMove.Name] = oMove.Data;
							else
								iMergeCnt = iMergeCnt + 1;
								KarmaObj.DB.MergeChar(oBuckets[oMove.Bucket][oMove.Name], oMove.Data);
							end
						end
					end

					KarmaChatDebug("DB upgrade: XServer moves to <" .. realmname .. ">: " .. iMoveCnt .. " moves, " .. iMergeCnt .. " merges.");
				end
			end

			KarmaData[KARMA_DB_L1.VERSION] = 14;
		end
		---
		---	End upgrade version 13 --> version 14
		---
	end

	KarmaChatDefault("DB upgrade complete.");

	KarmaChatDebugStack("DB upgrade via");
end

function	KarmaObj.DB.TankByClass()
	local	iRealmCnt, iMemberCnt, iRegLCnt, iRegDaysCnt = 0, 0, 0, 0;
	local	oTank, iDateFrom, iDateTo = { DEATHKNIGHT = {}, DRUID = {}, PALADIN = {}, WARRIOR = {} };

	local	oFactionDBs, sFaction, oFactionDB = { ["Alliance"] = KarmaAvEnKAlliance, ["Horde"] = KarmaAvEnKHorde };
	for sFaction, oFactionDB in pairs(oFactionDBs) do
		local	realmlist, realmname, realmdata;
		realmlist = oFactionDB[KARMA_DB_L1.REALMLIST] or {};

		local	oServers = {};
		local	oMovables = {};

		for realmname, realmdata in pairs(realmlist) do
			iRealmCnt = iRealmCnt + 1;

			local	bkey, bvalue;
			for bkey, bvalue in pairs(realmdata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
				local	mkey, mvalue;
				for mkey, mvalue in pairs(bvalue) do
					iMemberCnt = iMemberCnt + 1;

					local	sClass, oRegL = mvalue["CLASSEN"];
					if (sClass and mvalue["CHARACTERSPECIFIC"]) then
						local	oCharKey, oCharValue;
						for oCharKey, oCharValue in pairs(mvalue["CHARACTERSPECIFIC"]) do
							oRegL = oCharValue["REG_L"];
							if (oRegL) then
								iRegLCnt = iRegLCnt + 1;

								local	rgkey, rgvalue, rdkey, rdvalue;
								for rgkey, rgvalue in pairs(oRegL) do
									if (rgvalue["REG_DAYS"]) then
										iRegDaysCnt = iRegDaysCnt + 1;

										local	iDiff = rgvalue["RL_REGDIFF"];
										for rdkey, rdvalue in pairs(rgvalue["REG_DAYS"]) do
											if (rdvalue["RD_ROLE"] == 1) then
												local	iDate = rdvalue["REG_DAYKEY"];
												if (not iDateFrom or iDate < iDateFrom) then
													iDateFrom = iDate;
												end
												if (not iDateTo or iDate > iDateTo) then
													iDateTo = iDate;
												end

												if (mvalue["LEVEL"] < 36) then
													oTank[sClass][iDiff + 10] = (oTank[sClass][iDiff + 10] or 0) + 1;
												else
													oTank[sClass][iDiff] = (oTank[sClass][iDiff] or 0) + 1;
												end
											end
										end
									end
								end
							end
						end
					end
				end
			end
		end
	end

--	KarmaChatDefault("Across " .. iRealmCnt .. " servers, " .. iMemberCnt .. " players, " .. iRegLCnt .. " groupage sections, " .. iRegDaysCnt .. " groups:");
	KarmaChatDefault("Between " .. date("%Y-%m-%d", iDateFrom) .. " and " .. date("%Y-%m-%d", iDateTo) .. ": (class, total, normal, heroic)");
	local	sClass, iCounts;
	for sClass, iCounts in pairs(oTank) do
		KarmaChatDefault(sClass .. ": " .. ((iCounts[1] or 0) + (iCounts[2] or 0)) .. " (" .. (iCounts[1] or "") .. " | " .. (iCounts[2] or "") .. ", " .. (iCounts[11] or 0) .. ")");
	end
--	KarmaChatDefault("---");
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

-----------------------------------------
-- Global Lists
-----------------------------------------

function	KarmaObj.DB.ServerName()
	return KDBC.Realm;
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

-----------------------------------------
-- Common/Global
-----------------------------------------

function KarmaObj.DB.CG.RegionListGet()
	KarmaObj.ProfileStart("DB.CG.RegionListGet");
	local	CommonRegionList = KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.REGIONNAMES];
	KarmaObj.ProfileStop("DB.CG.RegionListGet");
	return CommonRegionList;
end

function KarmaObj.DB.CG.ZoneListGet()
	KarmaObj.ProfileStart("DB.CG.ZoneListGet");
	local	CommonZoneList = KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.ZONENAMES];
	KarmaObj.ProfileStop("DB.CG.ZoneListGet");
	return CommonZoneList;
end

function KarmaObj.DB.CG.LocaleTalentGet(iSpecID, sClassEN)
	KarmaObj.ProfileStart("DB.CG.LocaleTalentGet");
	local	oGlobalCommon = KarmaData[KARMA_DB_L1.COMMONLIST];
	local	sLocale = KARMA_DB_L2_C.LOCALE .. KDBC.Locale;
	local	oGlobalCommonLocale = oGlobalCommon[sLocale];
	local	LocaleTalents = oGlobalCommonLocale[KARMA_DB_L3_CL.TALENTS];
	if (type(LocaleTalents[iSpecID]) ~= "table") then
		local sRole = GetSpecializationRoleByID(iSpecID);
		if (sRole) then
			-- extra request for role plus check seems like a detour,
			-- as the Info also contains the role at pos #6
			local _, sName, sDescription, sIcon = GetSpecializationInfoByID(iSpecID);
			if (sName) then
				if (sClassEN == nil) then
					local	sClassLowercase = select(2, strsplit("_", sIcon));
					sClassEN = strupper(KarmaObj.UTF8.SubInChars(sClassLowercase, 1, 1))
								.. KarmaObj.UTF8.SubInChars(sClassLowercase, 2);
				end
				LocaleTalents[iSpecID] = { Name = sName, ClassEN = sClassEN, Desc = sDescription, Role = sRole };
			end
		end
	end
	KarmaObj.ProfileStop("DB.CG.LocaleTalentGet");
	return LocaleTalents[iSpecID];
end

--
--
--

local	oRegion2AreaID;

function KarmaObj.DB.CG.LocaleRegions(iAreaID, iContinent, iZone, sName)
	KarmaObj.ProfileStart("DB.CG.LocaleRegions");
	local	oGlobalCommon = KarmaData[KARMA_DB_L1.COMMONLIST];
	local	sLocale = KARMA_DB_L2_C.LOCALE .. KDBC.Locale;
	local	oGlobalCommonLocale = oGlobalCommon[sLocale];
	if (iAreaID == 312312312) then
		oGlobalCommonLocale[KARMA_DB_L3_CL.REGIONS] = nil;
	end
	local	LocaleRegions = oGlobalCommonLocale[KARMA_DB_L3_CL.REGIONS];
	if (type(LocaleRegions) ~= "table") then
		oGlobalCommonLocale[KARMA_DB_L3_CL.REGIONS] = { NAMES = {}, CONTZONE = {} };
		LocaleRegions = oGlobalCommonLocale[KARMA_DB_L3_CL.REGIONS];
	end

	if (type(iAreaID) == "number") then
		if (iAreaID < 0) then
			LocaleRegions.FETCHCTR = math.max(LocaleRegions.FETCHCTR or 0, - iAreaID);
		elseif (type(sName) == "string") then
			oRegion2AreaID = nil;
			LocaleRegions.NAMES[iAreaID] = sName;
			if ((type(iContinent) == "number") and (type(iZone) == "number")) then
				LocaleRegions.CONTZONE[iAreaID] = iContinent * 1000 + iZone;
			end
		end
	end
	KarmaObj.ProfileStop("DB.CG.LocaleRegions");
	if (type(iAreaID) == "number") then
		return LocaleRegions.NAMES[iAreaID], LocaleRegions.CONTZONE[iAreaID];
	else
		return LocaleRegions, LocaleRegions.FETCHCTR;
	end
end

local	oRegion2AreaID;

function KarmaObj.DB.CG.LocaleRegion2AreaID(sName)
	if (type(oRegion2AreaID) == "table") then
		return oRegion2AreaID[sName];
	end

	oRegion2AreaID = {};
	local	oRegions, k, v = KarmaObj.DB.CG.LocaleRegions();
	for k, v in pairs(oRegions.NAMES) do
		oRegion2AreaID[v] = k;
	end

	return oRegion2AreaID[sName];
end

function KarmaObj.DB.CG.RegionListMarkPVP(oRegionsBGsAndArenas)
	KarmaObj.ProfileStart("DB.CG.RegionListMarkPVP");

	local RL, k, v = KarmaObj.DB.CG.RegionListGet();
	if (next(oRegionsBGsAndArenas) ~= nil) then
		for k, v in pairs(RL) do
			if	(oRegionsBGsAndArenas[v.Name]) then
				KarmaChatDebug("<" .. v.Name .. ">: will be/is marked as pvp-region.");
				if (v[KARMA_DB_L3_CR.ISPVPZONE] ~= 1) then
					KarmaChatDefault("<" .. v.Name .. "> " .. KARMA_MSG_DBCLEAN_PVPREGIONMARKED);
					v[KARMA_DB_L3_CR.ISPVPZONE] = 1;
				end
			elseif (v[KARMA_DB_L3_CR.ISPVPZONE] == 1) then
				KarmaChatDebug("<" .. v.Name .. ">: marked as pvp-region, but shouldn't be?");
			end
		end
	else
		for k, v in pairs(RL) do
			if	(v.Name == KARMA_PVPZONE_WSG) or
				(v.Name == KARMA_PVPZONE_AB) or
				(v.Name == KARMA_PVPZONE_AV) or

				(v.Name == KARMA_PVPZONE_ES) or

				(v.Name == KARMA_PVPZONE_SA) or
				(v.Name == KARMA_PVPZONE_IOC) or
				(v.Name == KARMA_PVPZONE_WG) or

				(v.Name == KARMA_PVPZONE_BFG) or
				(v.Name == KARMA_PVPZONE_TP) or
				(v.Name == KARMA_PVPZONE_TB) then
				KarmaChatDebug("<" .. v.Name .. ">: will be/is marked as pvp-region.");
				if (v[KARMA_DB_L3_CR.ISPVPZONE] ~= 1) then
					KarmaChatDefault("<" .. v.Name .. "> " .. KARMA_MSG_DBCLEAN_PVPREGIONMARKED);
					v[KARMA_DB_L3_CR.ISPVPZONE] = 1;
				end
			end
		end
	end

	KarmaObj.ProfileStop("DB.CG.RegionListMarkPVP");
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

-----------------------------------------
-- Common/PerFaction (but not per server)
-----------------------------------------

-- CommonQuestListGet
function KarmaObj.DB.CF.QuestNameListGet()
	KarmaObj.ProfileStart("DB.CF.QuestNameListGet");

	local	oCF, CommonQuestList = KDBC.CommonFactionObject;
	if (oCF ~= nil) then
		CommonQuestList = oCF[KARMA_DB_L3_CF_QUESTNAMES];
	end

	KarmaObj.ProfileStop("DB.CF.QuestNameListGet");
	return CommonQuestList;
end

-- CommonQuestInfoListGet
function KarmaObj.DB.CF.QuestInfosListGet()
	KarmaObj.ProfileStart("DB.CF.QuestInfosListGet");


	local	oCF, CommonQuestInfoList = KDBC.CommonFactionObject;
	if (oCF ~= nil) then
		CommonQuestInfoList = oCF[KARMA_DB_L3_CF_QUESTINFOS];
	end

	KarmaObj.ProfileStop("DB.CF.QuestInfosListGet");
	return CommonQuestInfoList;
end

-- CommonQuestAdd
function KarmaObj.DB.CF.QuestListAdd(Quest, Faction, ExtID)
	local Index, PerfectIndex;

	if ((ExtID == nil) and (Faction == nil)) then
		KarmaChatDebug("DB.CF.QuestListAdd: ExtID == nil! " .. debugstack());
	end

	local	oCommonFaction = KDBC.CommonFactionObject;
	if (FactionKey and (FactionKey ~= KDBC.PlayerFaction)) then
		--[[
		if (KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.FACTION][FactionKey] == nil) then
			-- happens at database conversion for the faction the current char is NOT on
			KOH.TableInit(KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.FACTION], FactionKey);
			KOH.TableInit(KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.FACTION][FactionKey], KARMA_DB_L3_CF_QUESTNAMES);
			KOH.TableInit(KarmaData[KARMA_DB_L1.COMMONLIST][KARMA_DB_L2_C.FACTION][FactionKey], KARMA_DB_L3_CF_QUESTINFOS);
		end
		]]--
		if (not IsAddOnLoaded("KarmaDB" .. FactionKey)) then
			local	iLoaded, sWhyNot = LoadAddOn("KarmaDB" .. FactionKey);
			if (not iLoaded) then
				KarmaChatDefault("Cannot access database for faction " .. FactionKey .. ": Failed to load load-on-demand module. (" .. sWhyNot .. ")");
				KarmaChatDebug(debugstack());
				return
			end
		end

		local	xFactionDB = _G["KarmaAvEnK" .. FactionKey];
		if (type(xFactionDB) ~= "table") then
			KarmaChatDefault("Cannot access database for faction " .. FactionKey .. ": Failed to initialize load-on-demand module properly.");
			KarmaChatDebug(debugstack());
			return
		end

		oCommonFaction = xFactionDB[KARMA_DB_L1.COMMONLIST];
		if (oCommonFaction == nil) then
			-- happens at database conversion for the faction the current char is NOT on
			KOH.TableInit(xFactionDB, KARMA_DB_L1.COMMONLIST);
			KOH.TableInit(xFactionDB[KARMA_DB_L1.COMMONLIST], KARMA_DB_L3_CF_QUESTNAMES);
			KOH.TableInit(xFactionDB[KARMA_DB_L1.COMMONLIST], KARMA_DB_L3_CF_QUESTINFOS);

			oCommonFaction = xFactionDB[KARMA_DB_L1.COMMONLIST];
		end
	end

	local	CommonQuestNamesList = oCommonFaction[KARMA_DB_L3_CF_QUESTNAMES];
	local	CommonQuestInfosList = oCommonFaction[KARMA_DB_L3_CF_QUESTINFOS];
	if (CommonQuestNamesList) then
		local Count = 0;
		for k, v in pairs(CommonQuestNamesList) do
			Count = Count + 1;
			if (v == Quest) then
				if (ExtID) then
					local VExtID;
					if (CommonQuestInfosList[k]) then
						VExtID = CommonQuestInfosList[k].ExtID;
					end
					if (VExtID) then
						if (ExtID == VExtID) then
							PerfectIndex = k;
							Index = k;
						end
					else
						Index = k;
					end
				else
					Index = k;
				end
			end
		end

		-- if the perfect Index was before a non-perfect one, Index got overridden, restore from PerfectIndex
		if (PerfectIndex) then
			Index = PerfectIndex;
		end

		if (Index == nil) then
			Index = 1 + Count;
			CommonQuestNamesList[Index] = Quest;
		end

		if (ExtID) then
			if (CommonQuestInfosList[Index] == nil) then
				CommonQuestInfosList[Index] = {};
			end
			CommonQuestInfosList[Index].ExtID = ExtID;
		end
	end

	return Index;
end

--###############################################################################################

function	KarmaObj.DB.CF.QuestInfoCleanup()
	local	oCommonFaction = KDBC.CommonFactionObject;
	local	oRealmlist = KDBC.Faction[KARMA_DB_L1.REALMLIST];

	local	oStore = {};
	local	realmname, realmdata;
	for realmname, realmdata in pairs(oRealmlist) do
		local	bkey, bvalue;
		for bkey, bvalue in pairs(realmdata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
			local	mkey, mvalue;
			for mkey, mvalue in pairs(bvalue) do
				local charspecificlist, ckey, cvalue = mvalue[KARMA_DB_L5_RRFFM_CHARACTERS];
				for ckey, cvalue in pairs(charspecificlist) do
					local charquestlist, qkey, qvalue = cvalue[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
					if (charquestlist) then
						for qkey, qvalue in pairs(charquestlist) do
							if (qvalue > 0) then
								KarmaChatDebug("Positive QuestID in database, upgrade error, can't cleanup QuestInfo/-Ex");
								return
							end

							oStore[- qvalue] = (oStore[- qvalue] or 0) + 1;
						end
					end

					local charquestexlist = cvalue[KARMA_DB_L6_RRFFMCC_QUESTEXLIST];
					if (charquestexlist) then
						for qkey, qvalue in pairs(charquestexlist) do
							if (qkey > 0) then
								KarmaChatDebug("Positive QuestID in database, upgrade error, can't cleanup QuestInfo/-Ex");
								return
							end

							oStore[- qkey] = (oStore[- qkey] or 0) + 1;
						end
					end
				end
			end
		end
	end

	--
	--

	local	CommonQuestNamesList = oCommonFaction[KARMA_DB_L3_CF_QUESTNAMES];
	local	CommonQuestInfosList = oCommonFaction[KARMA_DB_L3_CF_QUESTINFOS];

	local	oQuestNamesNew = {};
	local	oQuestInfosNew = {};

	local	oMap = {};
	local	qidnew, qkey, qvalue = 1;
	for qkey, qvalue in pairs(oStore) do
		oMap[qkey] = qidnew;
		oQuestNamesNew[qidnew] = CommonQuestNamesList[qkey];
		oQuestInfosNew[qidnew] = CommonQuestInfosList[qkey];
		qidnew = qidnew + 1;
	end

	oCommonFaction[KARMA_DB_L3_CF_QUESTNAMES] = oQuestNamesNew;
	oCommonFaction[KARMA_DB_L3_CF_QUESTINFOS] = oQuestInfosNew;

	--
	--

	local	realmname, realmdata;
	for realmname, realmdata in pairs(oRealmlist) do
		local	bkey, bvalue;
		for bkey, bvalue in pairs(realmdata[KARMA_DB_L4_RRFF.MEMBERBUCKETS]) do
			local	mkey, mvalue;
			for mkey, mvalue in pairs(bvalue) do
				local charspecificlist, ckey, cvalue = mvalue[KARMA_DB_L5_RRFFM_CHARACTERS];
				for ckey, cvalue in pairs(charspecificlist) do
					local charquestlist, qkey, qvalue = cvalue[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
					if (charquestlist) then
						local	oNew = {};
						for qkey, qvalue in pairs(charquestlist) do
							tinsert(oNew, - oMap[- qvalue]);
						end
						cvalue[KARMA_DB_L6_RRFFMCC_QUESTIDLIST] = oNew;
					end

					local charquestexlist = cvalue[KARMA_DB_L6_RRFFMCC_QUESTEXLIST];
					if (charquestexlist) then
						local	oNew = {};
						for qkey, qvalue in pairs(charquestexlist) do
							oNew[ - oMap[- qkey]] = qvalue;
						end
						cvalue[KARMA_DB_L6_RRFFMCC_QUESTEXLIST] = oNew;
					end
				end
			end
		end
	end
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

-----------------------------------------
-- Server/Faction Object
-----------------------------------------

function	KarmaObj.DB.FactionCacheInit(bInit)
	local	bSet = KDBC.FactionObjectCache == nil;
	if (KDBC.Realm and KDBC.PlayerFaction) then
		local	oFactionDB = _G["KarmaAvEnK" .. KDBC.PlayerFaction];
		if (type(oFactionDB) == "table") then
			KDBC.FactionObjectCache = oFactionDB[KARMA_DB_L1.REALMLIST][KDBC.Realm];
		end
	end

	if (bSet) then
		if (KDBC.FactionObjectCache == nil) then
			if (bInit) then
				DEFAULT_CHAT_FRAME:AddMessage("Karma: |cFFFF6060Initialization error! <" .. (KDBC.Realm or "???") .. "; " .. (KDBC.PlayerFaction or "???") .. ">|r Cannot use database, cannot store or load information.");
			end
		else
			DEFAULT_CHAT_FRAME:AddMessage("Karma: Loaded database for <" .. (KDBC.Realm or "???") .. "; " .. (KDBC.PlayerFaction or "???") .. ">.");
		end
	end

	return KDBC.FactionObjectCache ~= nil;
end

-- Karma_Faction_GetFactionObject -> KarmaObj.DB.FactionCacheGet
KarmaObj.DB.FactionCacheError = {};
function	KarmaObj.DB.FactionCacheGet(bNoInit)
	KarmaObj.ProfileStart("DB.FactionCacheGet");
	if (KDBC.FactionObjectCache == nil) then
		if (not bNoInit) then
			KarmaObj.DB.FactionCacheInit();
		end

		tinsert(KarmaObj.DB.FactionCacheError, debugstack());
		DEFAULT_CHAT_FRAME:AddMessage(debugstack());
	end
	KarmaObj.ProfileStop("DB.FactionCacheGet");
	return KDBC.FactionObjectCache;
end

KarmaObj.DB.FSGError = {};
function	KarmaObj.DB.FactionServerGet(sServer)
	if (not sServer) then
		local	sData = debugstack();
		KarmaObj.DB.FSGError[sData] = (KarmaObj.DB.FSGError[sData] or 0) + 1;

		return KarmaObj.DB.FactionCacheGet();
	elseif (sServer == KDBC.Realm) then
		return KarmaObj.DB.FactionCacheGet();
	else
		local	iServerID, oServerFaction = KarmaObj.DB.ServerCreate(KDBC.Faction, sServer, true);
		return oServerFaction;
	end
end

-- cross-faction FactionObject (no caching)
function	KarmaObj.DB.FactionOtherGet()
	KarmaObj.ProfileStart("DB.FactionOtherGet");

	local	xFaction;
	if (KDBC.PlayerFaction == "Horde") then
		xFaction = "Alliance";
	elseif (KDBC.PlayerFaction == "Alliance") then
		xFaction = "Horde";
	end

	local	oXFaction;
	if (KDBC.Realm and xFaction) then
		local	xFactionDB = _G["KarmaAvEnK" .. xFaction];
		if (type(xFactionDB) == "table") then
			oXFaction = xFactionDB[KARMA_DB_L1.REALMLIST][KDBC.Realm];
		end
	end

	KarmaObj.ProfileStop("DB.FactionOtherGet");
	return	oXFaction;
end

function	KarmaObj.DB.FactionXHoldCreate(sFaction)
	local	oServer = KarmaData[KARMA_DB_L1.XFACTIONHOLD][KDBC.Realm];
	if (type(oServer) ~= "table") then
		KarmaData[KARMA_DB_L1.XFACTIONHOLD][KDBC.Realm] = {};
		oServer = KarmaData[KARMA_DB_L1.XFACTIONHOLD][KDBC.Realm];
	end

	if (sFaction == nil) then
		sFaction = KDBC.PlayerFaction;
	end

	local	oFaction = oServer[sFaction];
	if (type(oFaction) ~= "table") then
		oServer[sFaction] = {};
		oFaction = oServer[sFaction];
	end
end

function	KarmaObj.DB.FactionXHoldGet(sFaction)
	local	oServer = KarmaData[KARMA_DB_L1.XFACTIONHOLD][KDBC.Realm];
	if (type(oServer) == "table") then
		if (sFaction == nil) then
			sFaction = KDBC.PlayerFaction;
		end

		local	oFaction = oServer[sFaction];
		if (type(oFaction) == "table") then
			return oFaction;
		end
	end
end

function	KarmaObj.DB.ServerListGet(bAll)
	local	oNames, k, v = {};
	for k, v in pairs(KDBC.Faction[KARMA_DB_L1.REALMLIST]) do
		if ((k ~= KDBC.Realm) or bAll) then
			tinsert(oNames, k);
		end
	end
	table.sort(oNames);
	return oNames;
end

--
--
--

local	function	NameToNameServer(sMemberName, sMemberServer)
	if (sMemberName == nil) or (sMemberName == "") then
		return nil;
	end

	if (strfind(sMemberName, '@', 1, true)) then
		sMemberName, sMemberServer = strsplit('@', sMemberName);
	else
		if (sMemberServer == "") then
			sMemberServer = nil;
		end
		sMemberServer = sMemberServer or KDBC.Realm;
	end

	return sMemberName, sMemberServer;
end

function	Karma_MemberList_GetObject(sMemberName, sMemberServer, lMembers, sMembersServer, bAdd)
	sMemberName, sMemberServer = NameToNameServer(sMemberName, sMemberServer);
	if (sMemberName == nil) then
		return nil;
	end

	if ((sMembersServer == nil) or (sMembersServer ~= sMemberServer)) then
		lMembers = KarmaObj.DB.SF.MemberListGet(sMemberServer or KDBC.Realm);
	end

	if (lMembers == nil) then
		KarmaChatDebugStack("lMembers nil: " .. (sMemberServer or "??") .. ", " .. (sMembersServer or "??") .. " - " .. (sMemberServer or KDBC.Realm or "??"));
	end

	local	sBucketName = KarmaObj.NameToBucket(sMemberName);
	local	oMember = lMembers[sBucketName][sMemberName];
	if ((oMember == nil) and bAdd) then
		oMember = {};
		lMembers[sBucketName][sMemberName] = oMember;
	end

	return oMember;
end

function	Karma_MemberList_Remove(sMemberName, sMemberServer)
	sMemberName, sMemberServer = NameToNameServer(sMemberName, sMemberServer);

	local	lMembers = KarmaObj.DB.SF.MemberListGet(sMemberServer or KDBC.Realm);
	local	sBucketName = KarmaObj.NameToBucket(sMemberName);
	local	oMember = lMembers[sBucketName][sMemberName];
	if (oMember) then
		lMembers[sBucketName][sMemberName] = nil;
	end

	KarmaWindow_Update();
end

--
--- accessors
--

-- old per s/f logic
function	KarmaObj.DB.SF.QuestListGet()
	KarmaObj.ProfileStart("Karma_Faction_GetQuestList");
	local	oFaction = KarmaObj.DB.FactionCacheGet();
	local	lQuests = oFaction[KARMA_DB_L4_RRFF.QUESTNAMES];
	KarmaObj.ProfileStop("Karma_Faction_GetQuestList");
	return lQuests;
end

-- old per s/f logic
function	KarmaObj.DB.SF.ZoneListGet()
	KarmaObj.ProfileStart("Karma_Faction_GetZoneList");
	local	oFaction = KarmaObj.DB.FactionCacheGet();
	local	lZones = oFaction[KARMA_DB_L4_RRFF.ZONENAMES];
	KarmaObj.ProfileStop("Karma_Faction_GetZoneList");
	return lZones;
end

function	KarmaObj.DB.SF.CharacterListGet()
	KarmaObj.ProfileStart("KarmaOjb.DB.SF.CharacterListGet");
	local	oFaction = KarmaObj.DB.FactionCacheGet();
	local	lCharacters = oFaction[KARMA_DB_L4_RRFF.CHARACTERLIST];
	KarmaObj.ProfileStop("KarmaOjb.DB.SF.CharacterListGet");
	return lCharacters;
end

function	KarmaObj.DB.SF.MemberListGet(sServer)
	KarmaObj.ProfileStart("KarmaObj.DB.SF.MemberListGet");
	local	oFaction = KarmaObj.DB.FactionServerGet(sServer);
	local	lMembers = oFaction[KARMA_DB_L4_RRFF.MEMBERBUCKETS];
	KarmaObj.ProfileStop("KarmaObj.DB.SF.MemberListGet");
	return lMembers;
end

function	KarmaObj.DB.SF.RecentlyJoinedInit()
	local	oData = KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm];
	if (#oData > 0) then
		return
	end

	local	oMembers = KarmaObj.DB.SF.MemberListGet(KDBC.Realm);
	local	sBucketName, oBucketValues;
	for sBucketName, BucketValue in pairs(oMembers) do -- Loop through each bucket
		local	sMemberName, oMember;
		for sMemberName, oMember in pairs(BucketValue) do -- Loop through contents of bucket
			local	iAt = oMember[KARMA_DB_L5_RRFFM.LASTSEEN] or 0;
			if (strfind(sMemberName, '@')) then
				local	sPlayer, sServer = strsplit('@', sMemberName);
				oData[1 + #oData] = { sPlayer = sPlayer, sServerFrom = sServer, iAt = iAt };
			else
				oData[1 + #oData] = { sPlayer = sMemberName, sServerFrom = KDBC.Realm, iAt = iAt };
			end
		end
	end
end

function	KarmaObj.DB.SF.RecentlyJoinedWipe()
	table.wipe(KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED]);
end

function	KarmaObj.DB.SF.RecentlyJoinedGet()
	return Karma_CopyTable(KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm]);
end

function	KarmaObj.DB.SF.RecentlyJoinedUpdate(oParty)
	local	oData, iCnt, i = KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm];

	local	oReverse, bResort = {};

	-- first, run over the whole list and re-fetch the timestamps
	iCnt = #oData;
	for i = 1, iCnt do
		local	sCombined = oData[i].sPlayer .. "@" .. oData[i].sServerFrom;
		if (oReverse[sCombined]) then
			-- duplicate?!
			oData[oReverse[sCombined]].iAt = 0;
		end
		oReverse[sCombined] = i;

		local	oMember;
		if (oData[i].sServerFrom ~= KDBC.Realm) then
			oMember = Karma_MemberList_GetObject(oData[i].sPlayer, oData[i].sServerFrom);
		else
			oMember = Karma_MemberList_GetObject(oData[i].sPlayer);
		end

		if (oMember == nil) then
			-- deleted: set timestamp to 0
			oData[i].iAt = 0;
			bResort = true;
		elseif (oData[i].iAt ~= oMember[KARMA_DB_L5_RRFFM.LASTSEEN]) then
			-- updated: refresh timestamp to most recent
			iCnt = #oData + 1;
			oData[iCnt] = Karma_CopyTable(oData[i]);
			oData[i].iAt = 0;
			oData[iCnt].iAt = oMember[KARMA_DB_L5_RRFFM.LASTSEEN];
			oReverse[sCombined] = iCnt;
			bResort = true;
		end
	end

	-- second, add the players from oParty and dump those above the 250th entry
	local	oRealmGrp, sCombined, oCombined = KDBC.Faction[KARMA_DB_L1.REALMGROUPS][KDBC.Realm];
	for sCombined, oCombined in pairs(oParty) do
		if (not strfind(sCombined, '@')) then
			sCombined = sCombined .. "@" .. KDBC.Realm;
		end

		local	iOld = oReverse[sCombined];
		if (iOld == nil) then
			local	sPlayer, sServer = strsplit('@', sCombined);

			local	iAt = oCombined[KARMA_DB_L5_RRFFM.LASTSEEN] or 1;
			oRealmGrp[sServer] = math.max(iAt, oRealmGrp[sServer] or 1);

			KarmaChatDebug("MRU list: adding player " .. sCombined);
			oData[1 + #oData] = { sPlayer = sPlayer, sServerFrom = sServer, iAt = iAt };
		elseif (oData[iOld].iAt ~= oCombined[KARMA_DB_L5_RRFFM.LASTSEEN]) then
			iCnt = #oData + 1;
			oData[iCnt] = Karma_CopyTable(oData[iOld]);
			oData[iOld].iAt = 0;
			oData[iCnt].iAt = oCombined[KARMA_DB_L5_RRFFM.LASTSEEN];
			oReverse[sCombined] = iCnt;
			bResort = true;
		end
	end

	if (bResort) then
		-- keep the newest 250 or more:
		-- first, dump all 0ed out entries
		local	oDataNew = {};
		iCnt = #oData;
		for i = 1, iCnt do
			if (oData[i].iAt ~= 0) then
				oDataNew[1 + #oDataNew] = oData[i];
			end
		end

		if (#oDataNew > 300) then
			local	iExtra = #oDataNew;
			iCnt = math.min(#oDataNew, 250);
			for i = 1, iCnt do
				oDataNew[i] = oDataNew[i + 50];
			end
			for i = iCnt + 1, iExtra do
				oDataNew[i] = nil;
			end
		end

		KarmaChatDebug("MRU list: updating from " .. #KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm] .. " to " .. #oDataNew .. " entries.");

		KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm] = oDataNew;
	end
end

function	KarmaObj.DB.SF.RecentlyJoinedFixup()
	local	oData, iCnt, i = KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm];

	local	oReverse, bResort = {};

	-- first, run over the whole list and re-fetch the timestamps
	iCnt = #oData;
	for i = 1, iCnt do
		local	sCombined = oData[i].sPlayer .. "@" .. oData[i].sServerFrom;
		if (oReverse[sCombined]) then
			-- duplicate?!
			oData[oReverse[sCombined]].iAt = 0;
			bResort = true;
		end
		oReverse[sCombined] = i;
	end

	if (bResort) then
		-- keep the newest 250 or more:
		-- first, dump all 0ed out entries
		local	oDataNew = {};
		iCnt = #oData;
		for i = 1, iCnt do
			if (oData[i].iAt ~= 0) then
				oDataNew[1 + #oDataNew] = oData[i];
			end
		end

		if (#oDataNew > 300) then
			iCnt = math.min(#oDataNew, 250);
			for i = 1, iCnt do
				oDataNew[i] = oDataNew[i + 50];
			end
		end

		oData = oDataNew;
		KDBC.CommonFactionObject[KARMA_DB_L3_CF_RECENTLY_JOINED][KDBC.Realm] = oData;
	end

	iCnt = #oData;
	for i = 1, iCnt - 1 do
		local	j;
		for j = i + 1, iCnt do
			if (oData[j].iAt < oData[i].iAt) then
				local	oSwap = oData[j];
				oData[j] = oData[i];
				oData[i] = oSwap;
			end
		end
	end
end

--###############################################################################################

local	fSparseTestOrDelete = function(sName, oMember, bExecute)
	local	iCount = 0;
	if (oMember) then
		local	oMemPerChar = oMember[KARMA_DB_L5_RRFFM_CHARACTERS];
		if (type(oMemPerChar) == "table") then
			for sChar, oChar in pairs(oMemPerChar) do
				--[[
				-- minimal pointless data:
					["XP"] = 0,
					["XPLVL"] = 0,
					["XPLAST"] = 0,
					["XPMAX"] = 0,
					["PLAYED"] = 0,
					["PLAYEDLAST"] = <any value>,

					["ZONEIDLIST"] = {},
					["QUESTIDLIST"] = {},
					["QUESTEXLIST"] = {},
					["ACHIEVED"] = {},
					["REG_L"] = {},

				-- related field names
					KARMA_DB_L6_RRFFMCC_XP = "XP";
					KARMA_DB_L6_RRFFMCC_XPLAST = "XPLAST";
					KARMA_DB_L6_RRFFMCC_XPMAX = "XPMAX";
					KARMA_DB_L6_RRFFMCC_XPLVL = "XPLVL";

					KARMA_DB_L6_RRFFMCC_PLAYED = "PLAYED";
					KARMA_DB_L6_RRFFMCC_PLAYEDLAST = "PLAYEDLAST";

					KARMA_DB_L6_RRFFMCC_JOINEDLAST = "JOINEDLAST";

					KARMA_DB_L6_RRFFMCC_ZONEIDLIST = "ZONEIDLIST";
					KARMA_DB_L6_RRFFMCC_QUESTIDLIST = "QUESTIDLIST";
					KARMA_DB_L6_RRFFMCC_QUESTEXLIST = "QUESTEXLIST";
					KARMA_DB_L6_RRFFMCC_ACHIEVED = "ACHIEVED";
					KARMA_DB_L6_RRFFMCC_REGIONLIST = "REG_L";
				]]--

				local	iXP     = oChar[KARMA_DB_L6_RRFFMCC_XP];
				local	iPlayed = oChar[KARMA_DB_L6_RRFFMCC_PLAYED];
				local	iJoined = oChar[KARMA_DB_L6_RRFFMCC_JOINEDLAST];
				if (((iXP == nil) or (iXP == 0)) and ((iPlayed == nil) or (iPlayed == 0)) and ((iJoined == nil) or (iJoined == 0))) then
					local	sAnything = "";
					local	sKey, oData;
					for sKey, oData in pairs(oChar) do
						if ((sKey == KARMA_DB_L6_RRFFMCC_KARMA_ID) or
						    (sKey == KARMA_DB_L6_RRFFMCC_XP) or (sKey == KARMA_DB_L6_RRFFMCC_XPLAST) or
						    (sKey == KARMA_DB_L6_RRFFMCC_XPMAX) or (sKey == KARMA_DB_L6_RRFFMCC_XPLVL) or
						    (sKey == KARMA_DB_L6_RRFFMCC_PLAYED) or (sKey == KARMA_DB_L6_RRFFMCC_PLAYEDLAST)) then
							-- nothing to do: already pre-tested or not relevant
						elseif ((sKey == KARMA_DB_L6_RRFFMCC_ZONEIDLIST) or (sKey == KARMA_DB_L6_RRFFMCC_QUESTIDLIST) or
							(sKey == KARMA_DB_L6_RRFFMCC_QUESTEXLIST) or (sKey == KARMA_DB_L6_RRFFMCC_REGIONLIST) or
							(sKey == KARMA_DB_L6_RRFFMCC_ACHIEVED)) then
							if (not KOH.TableIsEmpty(oData)) then
								local	iCnt, sSub, oSub = 0;
								for sSub, oSub in pairs(oData) do
									iCnt = iCnt + 1;
								end
								sAnything = sAnything .. " " .. sKey .. "[" .. iCnt .. "]";
							end
						else	-- leaves JOINEDLAST
							sAnything = sAnything .. " " .. sKey;
						end
					end
					if (sAnything ~= "") then
						KarmaChatSecondary(sName .. "::" .. sChar .. " - " .. sAnything);
					else
						if (bExecute) then
							oMemPerChar[sChar] = nil;
							KarmaChatSecondary(sName .. "::" .. sChar .. " - DROPPED!");
						else
							KarmaChatSecondary(sName .. "::" .. sChar .. " - could drop!");
						end
						iCount = iCount + 1;
					end
				else
					local	sOutput = "";
					if (iXP and (iXP > 0)) then
						if (sOutput ~= "") then
							sOutput = sOutput .. " or";
						end
						sOutput = sOutput .. " xp > 0: " .. Karma_NilToString(iXP);
					end
					if (iPlayed and (iPlayed > 0)) then
						if (sOutput ~= "") then
							sOutput = sOutput .. " or";
						end
						sOutput = sOutput .. " played > 0: " .. Karma_NilToString(iPlayed);
					end
					if (iJoined and (iJoined > 0)) then
						if (sOutput ~= "") then
							sOutput = sOutput .. " or";
						end
						sOutput = sOutput .. " joined > 0: " .. Karma_NilToString(iJoined);
					end
					KarmaChatSecondary(sName .. "::" .. sChar .. " -" .. sOutput);
				end
			end
		end
	end

	return iCount;
end

-- operating on whole memberlist
function	KarmaObj.DB.SF.Sparsify(sPattern, bDrop)
	local	iDropped = 0;
	if (strsub(sPattern, strlen(sPattern)) == "*") then
		local	iLen = strlen(sPattern) - 1;
		local	sPat = strsub(sPattern, 1, iLen);

		local	lMembers = KarmaObj.DB.SF.MemberListGet(KDBC.Realm);
		local	sBucketName, oBucketValues;
		for sBucketName, BucketValue in pairs(lMembers) do -- Loop through each bucket
			local	sMemberName, oMember;
			for sMemberName, oMember in pairs(BucketValue) do -- Loop through contents of bucket
				if (strsub(sMemberName, 1, iLen) == sPat) then
					iDropped = iDropped + fSparseTestOrDelete(sMemberName, oMember, bDrop);
				end
			end
		end
	else
		local	oMember = Karma_MemberList_GetObject(sPattern);
		iDropped = fSparseTestOrDelete(sPattern, oMember, bDrop);
	end

	return iDropped;
end

function	KarmaObj.DB.SF.NameToID(sServer, oFactionDB)
	if (oFactionDB == nil) then
		oFactionDB = _G["KarmaAvEnK" .. KDBC.PlayerFaction];
	end

	if (type(oFactionDB) ~= "table") then
		return
	end

	local	oServerlist = oFactionDB[KARMA_DB_L1.REALMLIST];
	if (type(oServerlist) ~= "table") then
		return
	end

	local	oServerFaction = oServerlist[sServer];
	if (type(oServerFaction) ~= "table") then
		return
	end

	return oServerFaction[KARMA_DB_L4_RRFF.SERVER_ID], oServerFaction;
end

function	KarmaObj.DB.SF.IDToName(iServerID)
	local	oFactionDB = _G["KarmaAvEnK" .. KDBC.PlayerFaction];
	if (type(oFactionDB) ~= "table") then
		return
	end

	local	oServerIDs = oFactionDB[KARMA_DB_L1.REALM_IDS];
	if (type(oServerIDs) ~= "table") then
		return
	end

	local	sServer = oServerIDs[iServerID];
	if (sServer == nil) then
		return
	end

	local	oServerlist = oFactionDB[KARMA_DB_L1.REALMLIST];
	if (type(oServerlist) ~= "table") then
		return
	end

	local	oServerFaction = oServerlist[sServer];
	if (type(oServerFaction) ~= "table") then
		return
	end

	return sServer, oServerFaction;
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

-----------------------------------------
-- Character Object
-----------------------------------------

function	KarmaObj.DB.CharacterConfigObjectGet()
	local	lCharacters = KarmaObj.DB.SF.CharacterListGet();
	local	CharConfig = lCharacters[KDBC.PlayerName][KARMA_DB_L5_RRFFC.CONFIGPERCHAR];
	return	CharConfig;
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

-----------------------------------------
-- History pointer data
-----------------------------------------

function	KarmaObj.DB.HistorySeen(sName, iNow)
	if (KDBC.FactionObjectCache) then
		if (KDBC.FactionObjectCache[KARMA_DB_L4_RRFF.HISTORY_MOVED][sName]) then
			KarmaChatDebug("Flagging member <" .. sName .. "> as recently-seen in history reference.");
			KDBC.FactionObjectCache[KARMA_DB_L4_RRFF.HISTORY_SEEN][sName] = iNow;
		end
	end
end

-----------------------------------------
-- Member Object
-----------------------------------------

function	KarmaObj.DB.M.AddedZoneSet(oMember, oMemberChar, iZoneID, bTrack)
	if (type(oMember) == "table") then
		if (oMember[KARMA_DB_L5_RRFFM.ADDED_IN] == nil) then
			local	sOut = "Setting initial zone to <" .. iZoneID .. ">";
			if (not bTrack) then
				sOut = sOut .. " (pvp)";
			end
			KarmaChatDebug(sOut);
			oMember[KARMA_DB_L5_RRFFM.ADDED_IN] = iZoneID;
		end

		if (not bTrack) then
			return
		end

		local	oZones, index, value = oMemberChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST];
		for index, value in pairs(oZones) do
			if (value == iZoneID) then
				return
			end
		end

		oZones[#oZones + 1] = iZoneID;
	end
end

function	KarmaObj.DB.M.AddedZoneGet(oMember)
	if (type(oMember) == "table") then
		return oMember[KARMA_DB_L5_RRFFM.ADDED_IN];
	end
end

function	KarmaObj.DB.M.Modified(oMember, sField)
	if (type(oMember) == "table") then
		oMember[KARMA_DB_L5_RRFFM.LASTCHANGED_TIME] = time();
		oMember[KARMA_DB_L5_RRFFM.LASTCHANGED_FIELD] = sField;
	end
end

-- regular contents --

function	KarmaObj.DB.M.KarmaGet(oMember)
	local	iKarma = 50;

	if (type(oMember) == "table") then
		iKarma = oMember[KARMA_DB_L5_RRFFM_KARMA] or iKarma;
	end

	return iKarma;
end

function	KarmaObj.DB.M.KarmaSet(oMember, iKarma)
	if (type(oMember) == "table") then
		local	iOld = oMember[KARMA_DB_L5_RRFFM_KARMA] or 50;
		if (iOld ~= iKarma) then
			KarmaObj.DB.M.Modified(oMember, "Karma value");
			if (iKarma == 50) then
				oMember[KARMA_DB_L5_RRFFM_KARMA] = nil;
			else
				oMember[KARMA_DB_L5_RRFFM_KARMA] = iKarma;
			end
		end
	end
end

-----------------------------------------
-- Member/Char Object
-----------------------------------------

function	KarmaObj.DB.MC.AchievementAdd(oMember, sChar, iAchievementID, iCriteriaIndex, iCritNum)
	if (type(oMember) == "table") then
		local	charobj = Karma_MemberObject_GetCharacterObject(oMember, sChar);
		if (charobj) then
			local	sAKey = "A" .. iAchievementID;	-- working key (LUA is not good with numeric keys in tables)
			KOH.TableInit(charobj, KARMA_DB_L6_RRFFMCC_ACHIEVED);
			KOH.TableInit(charobj[KARMA_DB_L6_RRFFMCC_ACHIEVED], sAKey);

			local	oAch = charobj[KARMA_DB_L6_RRFFMCC_ACHIEVED][sAKey];
			oAch.At = time();	-- singular criteria
			if (iCriteriaIndex) then
				local	sCKey = "C" .. iCriteriaIndex;
				oAch[sCKey] = time();
			end
			if (iCritNum) then
				oAch["CMAX"] = iCritNum;
			end

			KarmaObj.DB.M.Modified(oMember, "Achievement");
		end
	end
end

function	KarmaObj.DB.MC.AchievementListGet(oMember, sChar)
	if (type(oMember) == "table") then
		local	charobj = Karma_MemberObject_GetCharacterObject(oMember, sChar);
		if (type(charobj) == "table") then
			local	lAchievements = charobj[KARMA_DB_L6_RRFFMCC_ACHIEVED];
			if (type(lAchievements) == "table") then
				local	lResult, sAKey, oValues, sSubKey, oSubVal = {};
				for sAKey, oValues in pairs(lAchievements) do
					lResult[sAKey] = {};
					for sSubKey, oSubVal in pairs(oValues) do
						lResult[sAKey][sSubKey] = oSubVal;
					end
				end

				return lResult;
			end
		end
	end

	return nil;
end

--###############################################################################################

local	function	KarmaAvEnK_DB_MC_AccessByID(oChars, iID)
	if (type(oChars) ~= "table") then
		return
	end

	local	k, v;
	for k, v in pairs(oChars) do
		if (iID == v[KARMA_DB_L6_RRFFMCC_KARMA_ID]) then
			return v;
		end
	end
end

function	KarmaObj.DB.MC.NameToID(sCombined, oFactionDB)
	local	sName, sServer = sCombined;
	if (strfind(sCombined, '@')) then
		sName, sServer = strsplit('@', sCombined);
	else
		sServer = KDBC.Realm;
	end

	local	iServerID, oServerFaction = KarmaObj.DB.SF.NameToID(sServer, oFactionDB)
	if (type(oServerFaction) ~= "table") then
		return
	end

	local	oChar = oServerFaction[KARMA_DB_L4_RRFF.CHARACTERLIST][sName];
	if (type(oChar) ~= "table") then
		return
	end

	return oChar[KARMA_DB_L5_RRFFC.KARMA_ID];
end

function	KarmaObj.DB.MC.IDToName(iID)
	local	oFactionDB = _G["KarmaAvEnK" .. KDBC.PlayerFaction];
	if (type(oFactionDB) ~= "table") then
		KarmaChatDebug("MC.!IDToName(" .. tostring(iID) .. "): !oFactionDB");
		return
	end

	local	iServerID, sServer, oServer = bit.rshift(iID, 16);
	if (iServerID ~= 0) then
		sServer, oServer = KarmaObj.DB.SF.IDToName(iServerID);
	end

	if (type(oServer) ~= "table") then
		KarmaChatDebug("MC.!IDToName(" .. tostring(iID) .. "): !oServer (ID = " .. tostring(iServerID) .. ")");
		return
	end

	local	oChars = oServer[KARMA_DB_L4_RRFF.CHARACTER_IDS];
	if (type(oChars) ~= "table") then
		KarmaChatDebug("MC.!IDToName(" .. tostring(iID) .. "): !oChars");
		return
	end

	local	sName = oChars[iID];
	if (sName) then
		return sName, sServer;
	else
		KarmaChatDebug("MC.!IDToName(" .. tostring(iID) .. "): !oChars[iID]");
	end
end

local	function	KarmaAvEnK_DB_MC_Access(oChars, xCombined)
	local	iID = xCombined;
	if (type(xCombined) == "string") then
		iID = KarmaObj.DB.MC.NameToID(xCombined);
	end

	return KarmaAvEnK_DB_MC_AccessByID(oChars, iID);
end

local	function	KarmaAvEnK_DB_MC_InitCore(oMember, sCombined)
	if ((type(oMember) ~= "table") or (type(sCombined) ~= "string")) then
		return false;
	end

	KOH.TableInit(oMember, KARMA_DB_L5_RRFFM_CHARACTERS);

	local	iID = KarmaObj.DB.MC.NameToID(sCombined);
	if (iID == nil) then
		return false;
	end

	local	oChar = KarmaAvEnK_DB_MC_AccessByID(oMember[KARMA_DB_L5_RRFFM_CHARACTERS], iID);
	local	bCreated = oChar == nil;
	if (bCreated) then
		oChar = {};
		tinsert(oMember[KARMA_DB_L5_RRFFM_CHARACTERS], oChar);
	end

	Karma_FieldInitialize(oChar, KARMA_DB_L6_RRFFMCC_KARMA_ID, iID, true);

	return bCreated, oChar;
end

function	KarmaObj.DB.MC.InitMinimum(oMember, sCombined)
	local	bCreated = KarmaAvEnK_DB_MC_InitCore(oMember, sCombined);
	return bCreated;
end

function	KarmaObj.DB.MC.InitMinimumPlayer(oMember)
	local	bCreated = KarmaObj.DB.MC.InitMinimum(oMember, KDBC.PlayerName);
	return bCreated;
end

function	KarmaObj.DB.MC.InitFull(oMember, sCombined)
	local	bCreated, oTableChar = KarmaAvEnK_DB_MC_InitCore(oMember, sCombined);
	if (oTableChar) then
		Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_PLAYED, 0, true);
		Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_PLAYEDPVP, 0, true);
		Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_PLAYEDLAST, 0, true);

		Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_XP, 0, true);
		Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_XPLVL, 0, true);

	--	Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_XPLAST, 0, true);
	--	Karma_FieldInitialize(oTableChar, KARMA_DB_L6_RRFFMCC_XPMAX, 0, true);

		KOH.TableInit(oTableChar, KARMA_DB_L6_RRFFMCC_QUESTIDLIST);
		KOH.TableInit(oTableChar, KARMA_DB_L6_RRFFMCC_QUESTEXLIST);
		KOH.TableInit(oTableChar, KARMA_DB_L6_RRFFMCC_ZONEIDLIST);
		KOH.TableInit(oTableChar, KARMA_DB_L6_RRFFMCC_ACHIEVED);
		KOH.TableInit(oTableChar, KARMA_DB_L6_RRFFMCC_REGIONLIST);

		return bCreated
	end

	return false;
end

function	KarmaObj.DB.MC.InitFullPlayer(oMember)
	KarmaObj.DB.MC.InitFull(oMember, KDBC.PlayerName);
end

function	KarmaObj.DB.MC.Exists(oMember, sCombined)
	if ((type(oMember) ~= "table") or (type(sCombined) ~= "string")) then
		-- KarmaChatDebug("MC:!Exists(" .. tostring(oMember) .. ", " .. tostring(sCombined) .. "): Arg type invalid.");
		return false;
	end

	if (type(oMember[KARMA_DB_L5_RRFFM_CHARACTERS]) ~= "table") then
		-- KarmaChatDebug("MC:!Exists(" .. tostring(oMember) .. ", " .. tostring(sCombined) .. "): No char data at all.");
		return false;
	end

	local	oChar = KarmaAvEnK_DB_MC_Access(oMember[KARMA_DB_L5_RRFFM_CHARACTERS], sCombined);
	if (type(oChar) ~= "table") then
		-- KarmaChatDebug("MC:!Exists(" .. tostring(oMember) .. ", " .. tostring(sCombined) .. "): No char data for req. char.");
		return false;
	end

	return true;
end

function	KarmaObj.DB.MC.Get(oMember, sCombined)
	if (KarmaObj.DB.MC.Exists(oMember, sCombined)) then
		return KarmaAvEnK_DB_MC_Access(oMember[KARMA_DB_L5_RRFFM_CHARACTERS], sCombined);
	end
end

function	KarmaObj.DB.MC.GetPlayer(oMember)
	return KarmaObj.DB.MC.Get(oMember, KDBC.PlayerName);
end

--###############################################################################################

function	KarmaObj.DB.JoinedInInstance(oMember, sChar, sInstance)
	if (type(oMember) ~= "table") or (type(sChar) ~= "string") or (type(sInstance) ~= "string") then
		return false;
	end

	local	charobj = Karma_MemberObject_GetCharacterObject(oMember, sChar);
	if (charobj and charobj[KARMA_DB_L6_RRFFMCC_REGIONLIST]) then
		local	oRegions = charobj[KARMA_DB_L6_RRFFMCC_REGIONLIST];
		if (oRegions) then
			local	CommonRegionList = KarmaObj.DB.CG.RegionListGet();

			local	k, v;
			for k, v in pairs(oRegions) do
				local	iRegionID = v[KARMA_DB_L7_RRFFMCCRR_ID];
				local	sRegion = CommonRegionList[iRegionID].Name;
				if (string.find(sRegion, sInstance)) then
					return true;
				end
			end
		end
	end

	return false;
end

--###############################################################################################
--###############################################################################################
--###############################################################################################

function	KarmaObj.DB.FixupAchCMAX()
	-- CMAX wasn't stored properly at first, this fixes that
	local	lMembers = KarmaObj.DB.SF.MemberListGet(KDBC.Realm);
	local	sBucketName, oBucketValues;
	for sBucketName, BucketValue in pairs(lMembers) do -- Loop through each bucket
		local	sMemberName, oMember;
		for sMemberName, oMember in pairs(BucketValue) do -- Loop through contents of bucket
			local	oMemPerChar = oMember[KARMA_DB_L5_RRFFM_CHARACTERS];
			if (type(oMemPerChar) == "table") then
				for sChar, oChar in pairs(oMemPerChar) do
					if (type(oChar[KARMA_DB_L6_RRFFMCC_ACHIEVED]) == "table") then
						local	sAKey, oAch;
						for sAKey, oAch in pairs(oChar[KARMA_DB_L6_RRFFMCC_ACHIEVED]) do
							if (oAch.CMAX == nil) then
								local	iAch = tonumber(strsub(sAKey, 2));
								oAch.CMAX = GetAchievementNumCriteria(iAch);
								if (oAch.CMAX ~= nil) then
									local	_, sAchievementName = GetAchievementInfo(iAch);
									KarmaChatDebug("Fixup(CMAX): nil -> " .. oAch.CMAX .. " for " .. sMemberName .. " in data relating to " .. sChar);
								end
							end
						end
					end
				end
			end
		end
	end
end

--###############################################################################################
--###############################################################################################
--###############################################################################################
--[[
local	KARMA_DB_L5_RRFFI = {
			GUID = "GUID",
			ACTIONS = "ACTIONS",
			IGNORE = "IGN",
			TIMEOUT = "TIMEOUT",
		};
]]--

function	KarmaObj.DB.I24.Clean(oFaction)
	local	oIgn24 = oFaction[KARMA_DB_L4_RRFF.IGNORE24];
	if (next(oIgn24)) then
		local	iNow = time() - 86400;
		local	oDrop, k, v = {};
		for k, v in pairs(oIgn24) do
			if (v[KARMA_DB_L5_RRFFI.TIMEOUT] < iNow) then
				tinsert(oDrop, k);
			end
		end

		local	iCnt, i = #oDrop;
		for i = 1, iCnt do
			oIgn24[oDrop[i]] = nil;
		end
	end
end

function	KarmaObj.DB.I24.Add(sName, sGUID, sEvent, sAction, bIgnore)
	local	oFaction = KarmaObj.DB.FactionCacheGet();
	local	oIgn24 = oFaction[KARMA_DB_L4_RRFF.IGNORE24];
	if (oIgn24[sName] == nil) then
		oIgn24[sName] = { [ KARMA_DB_L5_RRFFI.ACTIONS ] = {} };
	end
	local	oData = oIgn24[sName];

	local	iNow = time();
	oData[KARMA_DB_L5_RRFFI.TIMEOUT] = iNow;
	if (sGUID) then
		oData[KARMA_DB_L5_RRFFI.GUID] = sGUID;
	end
	if (bIgnore ~= nil) then
		oData[KARMA_DB_L5_RRFFI.IGNORE] = bIgnore;
	end

	local	oActions = oData[KARMA_DB_L5_RRFFI.ACTIONS];
	if (#oActions > 0) then
		local	oPrevious = oActions[#oActions];
		if ((oPrevious.iTime == iNow) and (oPrevious.sEvent == sEvent) and (oPrevious.sAction == sAction)) then
			-- CHAT_MSG_* is sent once per chat window it appears in, only log once
			return
		end
	end

	oActions[#oActions + 1] = { iTime = iNow, sEvent = sEvent, sAction = sAction };
end

function	KarmaObj.DB.I24.Check(sName)
	local	oFaction = KarmaObj.DB.FactionCacheGet();
	local	oIgn24 = oFaction[KARMA_DB_L4_RRFF.IGNORE24];
	if (oIgn24[sName] ~= nil) then
		local	oData = oIgn24[sName];
		return oData[KARMA_DB_L5_RRFFI.IGNORE];
	end

	return false;
end

--
--- Import/Export
--

function KarmaObj.DB.ExportOne(Member)
	-- do global export of current server/faction if no name?
	local	sBucketName = KarmaObj.NameToBucket(Member);
	local	lMembers = Karma_Faction_GetMemberList(oFaction);
	local	tMember = lMembers[sBucketName][Member];
	if (tMember == nil) then
		KarmaChatDefault("Can't export " .. args[2] .. KARMA_WINEL_FRAG_COLONSPACE .. KARMA_MSG_COMMAND_NOTMEMBER);
		return;
	end

	-- to export, we need:
	-- (1) the Questnames to ID mapping
	-- (2) the Zones to ID mapping
	-- (3) the player data

	local KarmaTransRoot = nil;
	if (KARMATRANS_AVAILABLE ~= nil) then
		KarmaTransRoot = KarmaExp;
	else
		KarmaTransRoot = KarmaData["_EXP_"];
	end

	if (KarmaTransRoot == nil) then
		KarmaTransRoot = {};
		KarmaTransRoot["CSV"] = {};
		KarmaTransRoot["LUA"] = {};
		KarmaTransRoot["LUA"]["_M"] = {};
		KarmaTransRoot["LUA"]["_Q"] = {};
		KarmaTransRoot["LUA"]["_Z"] = {};
		KarmaTransRoot["LUA"]["_R"] = {};
		KarmaTransRoot["SFD"] = {};
		KarmaTransRoot["SFD"]["SERVER"] = GetRealmName();
		KarmaTransRoot["SFD"]["FACTION"] = KDBC.PlayerFaction;
		KarmaTransRoot["SFD"]["DBVERSION"] = KARMA_SUPPORTEDDATABASEVERSION;

		-- assign back
		if (KARMATRANS_AVAILABLE ~= nil) then
			KarmaExp = KarmaTransRoot;
		else
			KarmaData["_EXP_"] = KarmaTransRoot;
		end
	end

	-- for our import: just everything in Blizzard format
	-- must not add anything to this, as LUA also adds it into tMember...
	KarmaTransRoot["LUA"]["_M"][Member] = tMember;

	-- shortcuts
	local ExportRootLUA = KarmaTransRoot["LUA"];
	local ExportRootCSV = KarmaTransRoot["CSV"];

	ExportRootCSV[Member] = {};

	-- for import in a spreadsheet
	-- °: just a char noone uses in notes (hopefully) - any better ideas for a delimiter?
	local CSVText	=  "###P;"	.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.NAME])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM_KARMA])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.LEVEL])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.CLASS])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.CLASS_ID])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.GENDER])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.GUILD])
					.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM.RACE])
					.. ";°"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM_NOTES])
					.. "°";
	if (tMember[KARMA_DB_L5_RRFFM_TIMESTAMP] ~= nil) then
		CSVText	= CSVText
				.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM_TIMESTAMP][KARMA_DB_L5_RRFFM_TIMESTAMP_TRY])
				.. ";"		.. Karma_NilToEmptyString(tMember[KARMA_DB_L5_RRFFM_TIMESTAMP][KARMA_DB_L5_RRFFM_TIMESTAMP_SUCCESS]);
	end
	ExportRootCSV[Member]["_G"] = CSVText;

	-- let all the IDs make sense...
	local CQL = CommonQuestListGet();
	local CQIL = CommonQuestInfoListGet();
	local CZL = CommonZoneListGet();
	local RegionListToAdd = {};
	for mychar, infos in pairs(tMember[KARMA_DB_L5_RRFFM_CHARACTERS]) do
		-- convert general CHARACTERS data into CSV format
		ExportRootCSV[Member][mychar] = {};
		ExportRootCSV[Member][mychar]["_X"] = "###PC;" .. Member .. ";" .. mychar
											.. ";" .. Karma_NilToEmptyString(infos[KARMA_DB_L6_RRFFMCC_XPLAST])
											.. ";" .. Karma_NilToEmptyString(infos[KARMA_DB_L6_RRFFMCC_XPMAX])
											.. ";" .. Karma_NilToEmptyString(infos[KARMA_DB_L6_RRFFMCC_XP])
											.. ";" .. Karma_NilToEmptyString(infos[KARMA_DB_L6_RRFFMCC_PLAYEDLAST])
											.. ";" .. Karma_NilToEmptyString(infos[KARMA_DB_L6_RRFFMCC_PLAYED]);

		ExportRootCSV[Member][mychar]["_Q"] = {};
		for index, QID in pairs(infos[KARMA_DB_L6_RRFFMCC_QUESTIDLIST]) do
			local QIDKey = "Q"..QID;
			local QIDPos = - QID;
			if (ExportRootLUA["_Q"][QIDKey] == nil) then
				if (CQL[QIDPos] ~= nil) then
					ExportRootLUA["_Q"][QIDKey] = {};
					ExportRootLUA["_Q"][QIDKey]["ID"] = QID;
					ExportRootLUA["_Q"][QIDKey]["NAME"] = CQL[QIDPos];
					ExportRootLUA["_Q"][QIDKey]["INFOS"] = CQIL[QIDPos];

					local QName = CQL[QIDPos];
					if (QName == nil) then
						QName = "NIL";
					end
					ExportRootCSV[QIDKey] = "###Q2N;" .. QID .. ";" .. QName;

					-- currently only region ID, wanted originally also QObjectives saved...
					local QRegionID;
					if (CQIL[QIDPos] ~= nil) then
						QRegionID = CQIL[QIDPos].RegionID;
					end
					if (QRegionID ~= nil) then
						ExportRootCSV[QIDKey] = ExportRootCSV[QIDKey] .. ";" .. QRegionID;

						table.insert(RegionListToAdd, QRegionID);
					end
				end
			end
			-- last ";" to add completion infos, if available
			ExportRootCSV[Member][mychar]["_Q"][QIDKey] = "###Q2C;" .. QID .. ";" .. Member .. ";" .. mychar .. ";";
		end
		-- QUESTEXLIST is currently fucked up, thanks to a LUA change of maximum stupidity
		-- have to change the DB format again to force it to save the Objective-Nr. explicitly
		-- same for QUESTNAMES. *grmbls* => no action in DB v8

		ExportRootCSV[Member][mychar]["_Z"] = {};
		for index, ZID in pairs(infos[KARMA_DB_L6_RRFFMCC_ZONEIDLIST]) do
			local ZIDKey = "Z"..ZID;
			local ZIDPos = - ZID;
			if (ExportRootLUA["_Z"][ZIDKey] == nil) then
				ExportRootLUA["_Z"][ZIDKey] = {};
				ExportRootLUA["_Z"][ZIDKey]["ID"] = ZID;

				if (CZL[ZIDPos] ~= nil) then
					ExportRootLUA["_Z"][ZIDKey]["NAME"] = CZL[ZIDPos].Name;
					ExportRootLUA["_Z"][ZIDKey]["RID"] = CZL[ZIDPos].RegionID;

					local ZName = CZL[ZIDPos].Name;
					if (ZName == nil) then
						ZName = "NIL";
					end
					ExportRootCSV[ZIDKey] = "###Z2N;" .. ZID .. ";" .. ZName;

					local ZRegionID = CZL[ZIDPos].RegionID
					if (ZRegionID ~= nil) then
						ExportRootCSV[ZIDKey] = ExportRootCSV[ZIDKey] .. ";" .. ZRegionID;

						table.insert(RegionListToAdd, ZRegionID);
					end
				end
			end

			ExportRootCSV[Member][mychar]["_Z"][ZIDKey] = "###Z2C;" .. ZID .. ";" .. Member .. ";" .. mychar .. ";";
		end
	end

	if (#RegionListToAdd > 0) then
		local CRL = CommonRegionListGet();
		for index, RID in pairs(RegionListToAdd) do
			if (RID ~= 0) then
				local RIDKey = "R"..RID;
				if (ExportRootLUA["_R"][RIDKey] == nil) then
					ExportRootLUA["_R"][RIDKey] = {};
					ExportRootLUA["_R"][RIDKey]["ID"] = RID;
					ExportRootLUA["_R"][RIDKey]["NAME"] = CRL[RID].Name;

					local RName = CRL[RID].Name;
					if (RName == nil) then
						RName = "NIL";
					end
					ExportRootCSV[RIDKey] = "###R;" .. RID .. ";" .. RName;
				end
			end
		end
	end
end

function KarmaObj.DB.Import(args)
	local KarmaTransRoot = nil;
	if (KARMATRANS_AVAILABLE ~= nil) then
		KarmaTransRoot = KarmaImp;
	else
		KarmaTransRoot = KarmaData["_IMP_"];
	end

	if (type(KarmaTransRoot) ~= "table") then
		-- no *sensible* data
		KarmaChatDefault("No data to import at all.");
		return;
	end

	if KarmaTransRoot == {} then
		-- empty data
		KarmaChatDefault("Import data set empty (1).");
		return;
	end

	if (type(KarmaTransRoot["LUA"]) ~= "table") then
		-- *still* no sensible data
		KarmaChatDefault("Import data set empty (2).");
		return;
	end

	if KarmaTransRoot["LUA"] == {} then
		-- *still* empty data
		KarmaChatDefault("Import data set empty (3).");
		return;
	end

	if (type(KarmaTransRoot["SFD"]) ~= "table") then
		-- unverifyable source
		KarmaChatDefault("Import data set incomplete.");
		return;
	end

	local Server = KarmaTransRoot["SFD"]["SERVER"];
	local Faction = KarmaTransRoot["SFD"]["FACTION"];
	if  (GetRealmName() ~= Server) or
		(KDBC.PlayerFaction ~= Faction) then
		KarmaChatDefault("Import entries' source does not match. Data set is from server "..Karma_NilToString(Server)..", faction "..Karma_NilToString(Faction)..". Import aborted.");
		return;
	end

	local DBVersion = KarmaTransRoot["SFD"]["DBVERSION"];
	if (DBVersion ~= KARMA_SUPPORTEDDATABASEVERSION) then
		KarmaChatDefault("Import entries were exported from an incompatible version (v"..Karma_NilToString(DBVersion)..").");
		KarmaChatDefault("You will have to re-export the data with the current Karma version. Import aborted.");
		return;
	end

	if (args[2] ~= nil) then
		KarmaChatDefault("Trying to only import " .. args[2] .. KARMA_WINEL_FRAG_TRIDOTS);
	else
		KarmaChatDefault("Importing entries" .. KARMA_WINEL_FRAG_TRIDOTS);
	end

	KarmaChatSecondary("Starting import" .. KARMA_WINEL_FRAG_TRIDOTS);

	-- localize
	local ImportMember, ImportData;

	-- we only go from the LUA data, CSV is being ignored.
	-- shortcuts
	local ImportRootLUA = KarmaTransRoot["LUA"];

	-- TODO much later: insert the region info... (i.e. Zone <-> Region mappings)

	-- first get a mapping of the IDs in the data to our current IDs.
	local QIDImp2QIDIntern = {};
	local ImportQID;
	for ImportQID, ImportData in pairs(ImportRootLUA["_Q"]) do
		InternQID = Karma_QuestList_AddQuest(ImportData["NAME"]);
		QIDImp2QIDIntern[ImportQID] = {};
		QIDImp2QIDIntern[ImportQID].ID = InternQID;
	end

	local ZIDImp2ZIDIntern = {};
	local ImportZID;
	for ImportZID, ImportData in pairs(ImportRootLUA["_Z"]) do
		if (ImportData["NAME"] ~= nil) then
			InternZID = Karma_ZoneList_AddZone(ImportData["NAME"]);
			ZIDImp2ZIDIntern[ImportZID] = {};
			ZIDImp2ZIDIntern[ImportZID].ID = InternZID;
		end
	end

	-- now import the list data
	local ImportCount = 0;
	local lMembers = Karma_Faction_GetMemberList(oFaction);
	for ImportMember, ImportData in pairs(ImportRootLUA["_M"]) do
		local DoIt = true;
		if (args[2] ~= nil) then
			if (args[2] ~= ImportMember) then
				DoIt = false;
			else
				KarmaChatSecondary(args[2] .. " found, importing" .. KARMA_WINEL_FRAG_TRIDOTS);
			end
		end

		if (DoIt) then
			ImportCount = ImportCount + 1;

			local sBucketName = KarmaObj.NameToBucket(ImportMember);
			if (lMembers[sBucketName][ImportMember] == nil) then
				Karma_MemberList_Add(ImportMember);
				KarmaChatSecondary("Added "..ImportMember.." to Karma's list" .. KARMA_WINEL_FRAG_TRIDOTS);
			end

			local tMember = lMembers[sBucketName][ImportMember];

			-- now, don't *lose* data here, that would really be the opposite of why the whole export/import is done...

			-- Karma value... tricky one. take max. to allow multiple imports of the same data
			local DeltaImp = KarmaObj.DB.M.KarmaGet(ImportData) - 50;
			local DeltaIntern = KarmaObj.DB.M.KarmaGet(tMember) - 50;
			local ImpKarmaNew;
			if (DeltaIntern == 0) then
				ImpKarmaNew = 50 + DeltaImp;
			elseif (DeltaImp == 0) then
				ImpKarmaNew = 50 + DeltaIntern;
			elseif (DeltaImp > 0) and (DeltaIntern > 0) then
				ImpKarmaNew = 50 + max(DeltaImp, DeltaIntern);
			else
				ImpKarmaNew = 50 + min(DeltaImp, DeltaIntern);
			end
			if (ImpKarmaNew < 0) then
				ImpKarmaNew = 0;
			elseif (ImpKarmaNew > 100) then
				ImpKarmaNew = 100;
			end

			KarmaObj.DB.M.KarmaSet(tMember, ImpKarmaNew);

			if (tMember[KARMA_DB_L5_RRFFM.LEVEL] == 0) and (ImportData[KARMA_DB_L5_RRFFM.LEVEL] > 0) then
				tMember[KARMA_DB_L5_RRFFM.LEVEL] = ImportData[KARMA_DB_L5_RRFFM.LEVEL];
			end
			if (tMember[KARMA_DB_L5_RRFFM.CLASS] == "") and (strlen(Karma_NilToEmptyString(ImportData[KARMA_DB_L5_RRFFM.CLASS])) > 0) then
				tMember[KARMA_DB_L5_RRFFM.CLASS] = ImportData[KARMA_DB_L5_RRFFM.CLASS];
			end
			if (tMember[KARMA_DB_L5_RRFFM.GENDER] == "") and (type(ImportData[KARMA_DB_L5_RRFFM.GENDER]) == "number") then
				tMember[KARMA_DB_L5_RRFFM.GENDER] = ImportData[KARMA_DB_L5_RRFFM.GENDER];
			end
			if (tMember[KARMA_DB_L5_RRFFM.GUILD] == "") and (ImportData[KARMA_DB_L5_RRFFM.GUILD] ~= "") then
				tMember[KARMA_DB_L5_RRFFM.GUILD] = ImportData[KARMA_DB_L5_RRFFM.GUILD];
			end
			if (tMember[KARMA_DB_L5_RRFFM.RACE] == "") and (ImportData[KARMA_DB_L5_RRFFM.RACE] ~= "") then
				tMember[KARMA_DB_L5_RRFFM.RACE] = ImportData[KARMA_DB_L5_RRFFM.RACE];
			end

			-- Notes... same problem as above. add if missing to allow multiple imports of the same data
			-- this could backfire, if multiple different subsets of notes are added multiple times in different order
			-- I'd call that user error ;) /Kärbär
			if (string.find(tMember[KARMA_DB_L5_RRFFM_NOTES], ImportData[KARMA_DB_L5_RRFFM_NOTES], 1, true) == nil) then
				tMember[KARMA_DB_L5_RRFFM_NOTES] = tMember[KARMA_DB_L5_RRFFM_NOTES]  .. ImportData[KARMA_DB_L5_RRFFM_NOTES];
			end

			for mychar, infos in pairs(ImportData[KARMA_DB_L5_RRFFM_CHARACTERS]) do
				local MemChar = tMember[KARMA_DB_L5_RRFFM_CHARACTERS][mychar];
				if (MemChar ~= nil) then
					if (args[2] ~= nil) then
						KarmaChatDebug("K: "..ImportMember.."::"..mychar.." started.");
					end

					-- XPLAST, XPMAX:
					-- most recent update of the player to our current (XPLAST) and level max (XPMAX) exp
					-- XP:
					-- accumulation of grouped exp with the player
					-- therefore:
					-- the value with the larger XPMAX or if same, larger XPLAST, wins
					local MemXPMax = Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_XPMAX]);
					local ImpXPMax = Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_XPMAX]);
					if (MemXPMax < ImpXPMax) or
					   ((MemXPMax == ImpXPMax) and
					   (Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_XPLAST]) <= Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_XPLAST]))) then
					   	MemChar[KARMA_DB_L6_RRFFMCC_XPLAST] = infos[KARMA_DB_L6_RRFFMCC_XPLAST];
						MemChar[KARMA_DB_L6_RRFFMCC_XPMAX] = infos[KARMA_DB_L6_RRFFMCC_XPMAX];
					end
					if (Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_XP]) < Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_XP])) then
						MemChar[KARMA_DB_L6_RRFFMCC_XP] = infos[KARMA_DB_L6_RRFFMCC_XP];
					end

					-- PLAYEDLAST: timestamp of most recent update to PLAYED
					-- PLAYED: summed playtime
					-- therefore: take larger PLAYED, reset PLAYEDLAST
					MemChar[KARMA_DB_L6_RRFFMCC_PLAYEDLAST] = 0;
					if (Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_PLAYED]) < Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_PLAYED])) then
						MemChar[KARMA_DB_L6_RRFFMCC_PLAYED] = infos[KARMA_DB_L6_RRFFMCC_PLAYED];
					end

					local QIDIntern = {};
					for index, QID in pairs(infos[KARMA_DB_L6_RRFFMCC_QUESTIDLIST]) do
						local QIDKey = "Q"..QID;
						if (QIDImp2QIDIntern[QIDKey] ~= nil) then
							table.insert(QIDIntern, QIDImp2QIDIntern[QIDKey].ID);
						end
					end
					local S1, S2, S3;
					S1 = #MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
					S2 = #QIDIntern;
					MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST] = Karma_TableMerge2Into1(MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST], QIDIntern);
					S3 = #MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
					if (args[2] ~= nil) and (S2 > 0) then
						KarmaChatDebug("K: "..ImportMember.."::"..mychar..": Q "..S1.."+"..S2.."="..S3);
					end

					if (infos[KARMA_DB_L6_RRFFMCC_QUESTEXLIST] ~= nil) then
						for index, QIDEx in pairs(infos[KARMA_DB_L6_RRFFMCC_QUESTEXLIST]) do
							-- currently (v8) not really feasible :/
						end
					end

					local ZIDIntern = {};
					for index, ZID in pairs(infos[KARMA_DB_L6_RRFFMCC_ZONEIDLIST]) do
						local ZIDKey = "Z"..ZID;
						if (ZIDImp2ZIDIntern[ZIDKey] ~= nil) then
							table.insert(ZIDIntern, ZIDImp2ZIDIntern[ZIDKey].ID);
						end
					end
					S1 = #MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST];
					S2 = #ZIDIntern;
					MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST] = Karma_TableMerge2Into1(MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST], ZIDIntern);
					S3 = #MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST];
					if (args[2] ~= nil) and (S2 > 0) then
						KarmaChatDebug("K: "..ImportMember.."::"..mychar..": Q "..S1.."+"..S2.."="..S3);
					end

					if (args[2] ~= nil) then
						KarmaChatDebug("K: "..ImportMember.."::"..mychar.." ended.");
					end
				end
			end
		end
	end

	KarmaChatSecondary("Import complete.");
	KarmaChatDefault("Import complete, "..ImportCount.." entries transmogrified.");
end

function	KarmaObj.DB.ImpExpCleanup()
	if (KARMATRANS_AVAILABLE ~= nil) then
		KarmaExp = nil;
		KarmaImp = nil;
	else
		KarmaData["_EXP_"] = nil;
		KarmaData["_IMP_"] = nil;
	end
end

function	KarmaObj.DB.MergeChar(oCharA, oCharB)
	local	tMember = oCharA;
	local	ImportData = oCharB;

	-- Karma value... tricky one. take max. to allow multiple imports of the same data
	local DeltaImp = KarmaObj.DB.M.KarmaGet(ImportData) - 50;
	local DeltaIntern = KarmaObj.DB.M.KarmaGet(tMember) - 50;
	local ImpKarmaNew;
	if (DeltaIntern == 0) then
		ImpKarmaNew = 50 + DeltaImp;
	elseif (DeltaImp == 0) then
		ImpKarmaNew = 50 + DeltaIntern;
	elseif (DeltaImp > 0) and (DeltaIntern > 0) then
		ImpKarmaNew = 50 + max(DeltaImp, DeltaIntern);
	else
		ImpKarmaNew = 50 + min(DeltaImp, DeltaIntern);
	end
	if (ImpKarmaNew < 0) then
		ImpKarmaNew = 0;
	elseif (ImpKarmaNew > 100) then
		ImpKarmaNew = 100;
	end

	KarmaObj.DB.M.KarmaSet(tMember, ImpKarmaNew);

	if (tMember[KARMA_DB_L5_RRFFM.LEVEL] == 0) and (ImportData[KARMA_DB_L5_RRFFM.LEVEL] > 0) then
		tMember[KARMA_DB_L5_RRFFM.LEVEL] = ImportData[KARMA_DB_L5_RRFFM.LEVEL];
	end
	if (tMember[KARMA_DB_L5_RRFFM.CLASS] == "") and (strlen(Karma_NilToEmptyString(ImportData[KARMA_DB_L5_RRFFM.CLASS])) > 0) then
		tMember[KARMA_DB_L5_RRFFM.CLASS] = ImportData[KARMA_DB_L5_RRFFM.CLASS];
	end
	if (tMember[KARMA_DB_L5_RRFFM.GENDER] == "") and (type(ImportData[KARMA_DB_L5_RRFFM.GENDER]) == "number") then
		tMember[KARMA_DB_L5_RRFFM.GENDER] = ImportData[KARMA_DB_L5_RRFFM.GENDER];
	end
	if (tMember[KARMA_DB_L5_RRFFM.GUILD] == "") and (ImportData[KARMA_DB_L5_RRFFM.GUILD] ~= "") then
		tMember[KARMA_DB_L5_RRFFM.GUILD] = ImportData[KARMA_DB_L5_RRFFM.GUILD];
	end
	if (tMember[KARMA_DB_L5_RRFFM.RACE] == "") and (ImportData[KARMA_DB_L5_RRFFM.RACE] ~= "") then
		tMember[KARMA_DB_L5_RRFFM.RACE] = ImportData[KARMA_DB_L5_RRFFM.RACE];
	end

	-- Notes... same problem as above. add if missing to allow multiple imports of the same data
	-- this could backfire, if multiple different subsets of notes are added multiple times in different order
	-- I'd call that user error ;) /Kärbär
	if (ImportData[KARMA_DB_L5_RRFFM_NOTES]) then
		local	sNotesA = tMember[KARMA_DB_L5_RRFFM_NOTES] or "";
		local	sNotesB = ImportData[KARMA_DB_L5_RRFFM_NOTES] or "";
		if (string.find(sNotesA, sNotesB, 1, true) == nil) then
			tMember[KARMA_DB_L5_RRFFM_NOTES] = (tMember[KARMA_DB_L5_RRFFM_NOTES] or "") .. ImportData[KARMA_DB_L5_RRFFM_NOTES];
		end
		if (tMember[KARMA_DB_L5_RRFFM_NOTES] == "") then
			tMember[KARMA_DB_L5_RRFFM_NOTES] = nil;
		end
	end

	local	iImpIx, infos;
	for iImpIx, infos in pairs(ImportData[KARMA_DB_L5_RRFFM_CHARACTERS]) do
		local	MemChar, iMemIx, oMemData
		for iMemIx, oMemData in pairs(tMember[KARMA_DB_L5_RRFFM_CHARACTERS]) do
			if (oMemData[KARMA_DB_L6_RRFFMCC_KARMA_ID] == infos[KARMA_DB_L6_RRFFMCC_KARMA_ID]) then
				MemChar = oMemData;
				break
			end
		end

		if (MemChar ~= nil) then
			if (args[2] ~= nil) then
				KarmaChatDebug("K: "..ImportMember.."::"..iImpIx.." started.");
			end

			-- XPLAST, XPMAX:
			-- most recent update of the player to our current (XPLAST) and level max (XPMAX) exp
			-- XP:
			-- accumulation of grouped exp with the player
			-- therefore:
			-- the value with the larger XPMAX or if same, larger XPLAST, wins
			local MemXPMax = Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_XPMAX]);
			local ImpXPMax = Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_XPMAX]);
			if (MemXPMax < ImpXPMax) or
			   ((MemXPMax == ImpXPMax) and
			   (Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_XPLAST]) <= Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_XPLAST]))) then
			   	MemChar[KARMA_DB_L6_RRFFMCC_XPLAST] = infos[KARMA_DB_L6_RRFFMCC_XPLAST];
				MemChar[KARMA_DB_L6_RRFFMCC_XPMAX] = infos[KARMA_DB_L6_RRFFMCC_XPMAX];
			end
			if (Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_XP]) < Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_XP])) then
				MemChar[KARMA_DB_L6_RRFFMCC_XP] = infos[KARMA_DB_L6_RRFFMCC_XP];
			end

			-- PLAYEDLAST: timestamp of most recent update to PLAYED
			-- PLAYED: summed playtime
			-- therefore: take larger PLAYED, reset PLAYEDLAST
			MemChar[KARMA_DB_L6_RRFFMCC_PLAYEDLAST] = 0;
			if (Karma_NilToZero(MemChar[KARMA_DB_L6_RRFFMCC_PLAYED]) < Karma_NilToZero(infos[KARMA_DB_L6_RRFFMCC_PLAYED])) then
				MemChar[KARMA_DB_L6_RRFFMCC_PLAYED] = infos[KARMA_DB_L6_RRFFMCC_PLAYED];
			end

			local QIDIntern = {};
			for index, QID in pairs(infos[KARMA_DB_L6_RRFFMCC_QUESTIDLIST]) do
				local QIDKey = "Q"..QID;
				if (QIDImp2QIDIntern[QIDKey] ~= nil) then
					table.insert(QIDIntern, QIDImp2QIDIntern[QIDKey].ID);
				end
			end
			local S1, S2, S3;
			S1 = #MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
			S2 = #QIDIntern;
			MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST] = Karma_TableMerge2Into1(MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST], QIDIntern);
			S3 = #MemChar[KARMA_DB_L6_RRFFMCC_QUESTIDLIST];
			if (args[2] ~= nil) and (S2 > 0) then
				KarmaChatDebug("K: "..ImportMember.."::"..iImpIx..": Q "..S1.."+"..S2.."="..S3);
			end

			local ZIDIntern = {};
			for index, ZID in pairs(infos[KARMA_DB_L6_RRFFMCC_ZONEIDLIST]) do
				local ZIDKey = "Z"..ZID;
				if (ZIDImp2ZIDIntern[ZIDKey] ~= nil) then
					table.insert(ZIDIntern, ZIDImp2ZIDIntern[ZIDKey].ID);
				end
			end
			S1 = #MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST];
			S2 = #ZIDIntern;
			MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST] = Karma_TableMerge2Into1(MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST], ZIDIntern);
			S3 = #MemChar[KARMA_DB_L6_RRFFMCC_ZONEIDLIST];
			if (args[2] ~= nil) and (S2 > 0) then
				KarmaChatDebug("K: "..ImportMember.."::"..iImpIx..": Q "..S1.."+"..S2.."="..S3);
			end

			local	iQEx, oQEx;
			for iQEx, oQEx in pairs(infos[KARMA_DB_L6_RRFFMCC_QUESTEXLIST]) do
				if (MemChar[KARMA_DB_L6_RRFFMCC_QUESTEXLIST][iQEx] == nil) then
					MemChar[KARMA_DB_L6_RRFFMCC_QUESTEXLIST][iQEx] = oQEx;
				end
			end

			local	iDungeon, oDungeon;
			for iDungeon, oDungeon in pairs(infos[KARMA_DB_L6_RRFFMCC_REGIONLIST]) do
				tinsert(MemChar[KARMA_DB_L6_RRFFMCC_REGIONLIST], oDungeon);
			end

			local	iAch, oAch;
			for iAch, oAch in pairs(infos[KARMA_DB_L6_RRFFMCC_ACHIEVED]) do
				if (MemChar[KARMA_DB_L6_RRFFMCC_ACHIEVED][iAch] == nil) then
					MemChar[KARMA_DB_L6_RRFFMCC_ACHIEVED][iAch] = oAch;
				end
			end

			if (args[2] ~= nil) then
				KarmaChatDebug("K: "..ImportMember.."::"..iImpIx.." ended.");
			end
		end
	end
end