vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--
-- MobInfo2.lua
--
-- Main module of MobInfo-2 AddOn
-- Version:  2.97
--
-- MobInfo-2 is a World of Warcraft AddOn that provides you with useful
-- additional information about Mobs (ie. opponents/monsters). It adds
-- new information to the game's Tooltip when you hover with your mouse
-- over a mob. It also adds a numeric display of the Mobs health
-- and mana (current and max) to the Mob target frame.
--
-- MobInfo-2 is the continuation of the original "MobInfo" by Dizzarian,
-- combined with the original "MobHealth2" by Wyv. Both Dizzarian and
-- Wyv sadly no longer play WoW and stopped maintaining their AddOns.
-- I have "inhereted" MobInfo from Dizzarian and MobHealth-2 from Wyv
-- and now continue to update and improve the united result.
--

-- global vars
MI2_Debug = 0  -- 0=no debug info, 1=minimal debug info, 2=extensive debug info, 3=more extensive+event info
MI2_DebugItems = 0  -- 0=no item debug info, 1=show item ID and item value in tooltip
MI2_DB_VERSION = 6
MI2_IMPORT_DB_VERSION = 6

-- default initialization for all MobInfo database tables
-- this automatically gets overwritten by the database contents loaded from file
MobInfoDB               = { ["DatabaseVersion:0"] = { ver = MI2_DB_VERSION } }
MI2_CharTable   = { charCount = 0 }
MI2_ZoneTable   = { cnt = 0 }
MI2_ItemNameTable = {}
MobHealthPlayerDB = {}
MobHealthDB             = {     }

local MI2_CurrentTargets = {}
local MI2_RecentCorpses = {}
local MI2_NewCorpseIdx = 0
local MI2_CurrentCorpseIndex = nil
local MI2_LootFrameOpen = false

-- skinning loot table using localization independant item IDs:
--    Ruined Leather Scraps, Light Leather, Medium Leather, Heavy Leather, Thick Leather, Rugged Leather
--    Chimera Leather, Devilsaur Leather, Frostsaber Leather, Warbear Leather,
--    Light Hide, Medium Hide, Heavy Hide, Thick Hide, Rugged Hide, Shadowcat Hide, Thick Wolfhide
--    Scorpid Scale, Shiny Fish Scales, Red Whelp Scales, Turtle Scales, Black Whelp Scales, Brilliant Chromatic Scale
--    Black Dragonscale, Blue Dragonscale, Red Dragonscale, Green Dragonscale, Worn Dragonscale, Heavy Scorpid Scale
local miSkinLoot = { [2934]=1, [2318]=1, [2319]=1, [4234]=1, [4304]=1, [8170]=1, 
                    [15423]=1,[15417]=1,[15422]=1,[15419]=1,
                      [783]=1, [4232]=1, [4235]=1, [8169]=1, [8171]=1, [7428]=1, [8368]=1,
                     [8154]=1,[17057]=1, [7287]=1, [8167]=1, [7286]=1,[12607]=1,
                    [15416]=1,[15415]=1,[15414]=1,[15412]=1, [8165]=1,[15408]=1, }

-- cloth loot table using localization independant item IDs
-- Linen Cloth, Wool Cloth, Silk Cloth, Mageweave Cloth, Felcloth, Runecloth, Mooncloth
local miClothLoot = { [2589]=1, [2592]=1, [4306]=1, [4338]=1, [14256]=1, [14047]=1, [14342]=1 };

local MI2_ItemCollapseList = { [2725]=2725, [2728]=2725, [2730]=2725, [2732]=2725, 
                               [2734]=2725, [2735]=2725, [2738]=2725, [2740]=2725,[2742]=2725,
                               [2745]=2725, [2748]=2725, [2749]=2725, [2750]=2725, [2751]=2725 }

-- global MobInfo color constansts
mifontBlue = "|cff0000ff"
mifontItemBlue = "|cff2060ff"
mifontLightBlue = "|cff00e0ff"
mifontGreen = "|cff00ff00"
mifontRed = "|cffff0000"
mifontLightRed = "|cffff8080"
mifontGold = "|cffffcc00"
mifontGray = "|cff888888"
mifontWhite = "|cffffffff"
mifontSubWhite = "|cffbbbbbb"
mifontMageta = "|cffe040ff" -- old magenta: "|cffff00ff"
mifontYellow  = "|cffffff00"
mifontCyan   = "|cff00ffff"
mifontOrange = "|cffff7000"
MI2_QualityColor = { [1]=mifontGray, [2]=mifontWhite, [3]=mifontGreen, [4]=mifontItemBlue, [5]=mifontMageta, [6]=mifontOrange, [7]=mifontRed }


-----------------------------------------------------------------------------
-- MI2_GetMobData( mobName, mobLevel [, unitId] )
--
-- Get and return all the data that MobInfo knows about a given mob.
-- This is an externally available interface function that can be
-- called by other AddOns to access MobInfo data. It should be fast,
-- efficient, and easy to use
--
-- The data describing a Mob is returned in table form as described below.
--
-- To identify the mob you must supply its name and level. You can
-- optionally supply a "unitId" to get additional info:
--   mobName : name of mob, eg. "Forest Lurker"
--   mobLevel : mob level as integer number
--   unitId : optional WoW unit identification, should be either
--            "target" or "mouseover"
--
-- Examples:
--    A.   mobData = MI2_GetMobData( "Forest Lurker", 10 )
--    B.   mobData = MI2_GetMobData( "Forest Lurker", 10, "target" )
--
-- Return Value:
-- The return value is a LUA table with one table entry for each value that
-- MobInfo can know about a Mob. Note that table entries exist ONLY if the
-- corresponding value has actually been collected for the given Mob.
-- Unrecorded values do NOT exist in the table and thus evaluate to a NIL
-- expression.
--
-- Values you can get without "unitId" (as per Example A above):
--    mobData.healthMax  :  health maximum
--    mobData.xp         :  experience value
--    mobData.kills      :  number of times current player has killed this mob
--    mobData.minDamage  :  minimum damage done by mob
--    mobData.maxDamage  :  maximum damage done by mob
--    mobData.dps        :  dps of Mon against current player
--    mobData.loots      :  number of times this mob has been looted
--    mobData.emptyLoots :  number of times this mob gave empty loot
--    mobData.clothCount :  number of times this mob gave cloth loot
--    mobData.copper     :  total money loot of this mob as copper amount
--    mobData.itemValue  :  total item value loot of this mob as copper amount
--    mobData.mobType    :  mob type for special mobs: 1=normal, 2=rare/elite, 3=boss
--    mobData.r1         :  number of rarity 1 loot items (grey)
--    mobData.r2         :  number of rarity 2 loot items (white)
--    mobData.r3         :  number of rarity 3 loot items (green)
--    mobData.r4         :  number of rarity 4 loot items (blue)
--    mobData.r5         :  number of rarity 5 loot items (purple)
--    mobData.itemList   :  table that lists all recorded items looted from this mob
--                          table entry index gives WoW item ID, 
--                          table entry value gives item amount
--
-- Additional values you will get with "unitId" (as per Example B above):
--    mobData.class      :  class of mob as localized text
--    mobData.healthCur  :  current health of given unit
--    mobData.manaCur    :  current mana of given unit
--    mobData.manaMax    :  maximum mana for given unit
--
-- Code Example:
--    
--    local mobData = MI2_GetMobData( "Forest Lurker", 10 )
--    
--    if mobData.xp then
--        DEFAULT_CHAT_FRAME:AddMessage( "XP = "..mobData.xp ) 
--    end
--    
--    if mobData.copper and mobData.loots then
--        local avgLoot = mobData.copper / mobData.loots
--        DEFAULT_CHAT_FRAME:AddMessage( "average loot = "..avgLoot ) 
--    end
-----------------------------------------------------------------------------
function MI2_GetMobData( mobName, mobLevel, unitId )
        local mobData = {}
        local mobIndex = mobName..":"..mobLevel

    -- get mobs PPP and calculate max health
        local mobPPP = MobHealth_PPP(mobIndex)
        if mobPPP <= 0 then mobPPP = 1 end
        mobData.healthMax = floor(mobPPP * 100 + 0.5)

        if MI2_Debug > 2 then chattext( "M2DBG: MI2_GetMobData: name=["..mobName.."], level="..mobLevel..", exists: "..tostring(MobInfoDB[mobIndex] ~= nil) ) end

        -- obtain unit specific values if unitId is given
        if unitId then
                mobData.class = UnitClass(unitId)
                if UnitHealthMax(unitId) == 100 then
                        mobData.healthCur = floor(mobPPP * UnitHealth(unitId) + 0.5)
                else
                        mobData.healthCur = UnitHealth(unitId)
                end
                mobData.manaCur = UnitMana( unitId )
                mobData.manaMax = UnitManaMax( unitId )
        end

        -- decode basic mob info
        -- exit here if mob does not exist in DB, set color only if mob exists
        local mobInfo = MobInfoDB[mobIndex]
        if  not mobInfo then return mobData end
        mobData.color = GetDifficultyColor( mobLevel )

        MI2_GetMobDataFromMobInfo( mobInfo, mobData )

        return mobData
end -- MI2_GetMobData()


-----------------------------------------------------------------------------
-- MI2_GetMobDataFromMobInfo()
--
-- Extract all data describing a specific mob from a given mob database
-- record (called "mobInfo"). The various different fields within the
-- record get decoded and the resulting data is returned in a convenient
-- format that allows for easy further processing of the data. The return
-- format for the decoded data is called "mobData". The resulting "mobData"
-- structure is returned.
-----------------------------------------------------------------------------
function MI2_GetMobDataFromMobInfo( mobInfo, mobData )
        MI2_DecodeBasicMobData( mobInfo, mobData )
        MI2_DecodePlayerSpecificData( mobInfo, mobData, MI2_PlayerName )
        MI2_DecodeQualityOverview( mobInfo, mobData )
        MI2_DecodeMobLocation( mobInfo, mobData )
        MI2_DecodeItemList( mobInfo, mobData )
end -- MI2_GetMobDataFromMobInfo()


-----------------------------------------------------------------------------
-- MI2_DecodeBasicMobData()
--
-- Decode the basic mob data. This function is used by the public
-- "MI2_GetMobData()" and also by the Mob search routines.
-----------------------------------------------------------------------------
function MI2_DecodeBasicMobData( mobInfo, mobData, mobIndex )
        if mobIndex then
                mobInfo = MobInfoDB[mobIndex]
                if not mobInfo then
                        -- unknown mob is being looted
                        mobData.loots = 1
                        return
                end
        end

        -- decode mob basic info: loots, empty loots, experience, cloth count, money looted, item value looted, mob type
        mobData.mobType = 1
        if mobInfo.bi then
                local a,b,lt,el,cp,iv,cc,xp,mt,sc = string.find( mobInfo.bi, "(%d*)/(%d*)/(%d*)/(%d*)/(%d*)/(%d*)/(%d*)/(%d*)")
                mobData.loots           = tonumber(lt)
                mobData.emptyLoots      = tonumber(el)
                mobData.xp                      = tonumber(xp)
                mobData.clothCount      = tonumber(cc)
                mobData.copper          = tonumber(cp)
                mobData.itemValue       = tonumber(iv)
                mobData.mobType         = tonumber(mt) or 1
                mobData.skinCount       = tonumber(sc)
        end
end -- MI2_DecodeBasicMobData()


-----------------------------------------------------------------------------
-- MI2_DecodeMobLocation()
--
-- Decode mob location info, skip invalid location data
-- The location is encoded in the mob record entry "ml".
-- The decoded data is stored in the given "mobData" structure.
-----------------------------------------------------------------------------
function MI2_DecodeMobLocation( mobInfo, mobData, mobIndex )
        if mobIndex then
                mobInfo = MobInfoDB[mobIndex]
        end

        if mobInfo.ml then
                local a,b,x1,y1,x2,y2,c,z = string.find( mobInfo.ml, "(%d*)/(%d*)/(%d*)/(%d*)/(%d*)/(%d*)")
                mobData.location = {}
                mobData.location.x1     = tonumber(x1)
                mobData.location.y1     = tonumber(y1)
                mobData.location.x2     = tonumber(x2)
                mobData.location.y2     = tonumber(y2)
                mobData.location.c      = tonumber(c)
                mobData.location.z      = (tonumber(z) or 0)
                if not mobData.location.x1 or not mobData.location.x2 or 
                                not mobData.location.y1 or not mobData.location.y2 or 
                                not mobData.location.c or mobData.location.z == 0 then
                        mobData.location = nil
                end
        end
end -- MI2_DecodeMobLocation()


-----------------------------------------------------------------------------
-- MI2_DecodeQualityOverview()
--
-- Decode item quality data: loot count per item rarity category
-- The loot items quality overview is encoded in the mob record entry "qi".
-- The decoded data is stored in the given "mobData" structure.
-----------------------------------------------------------------------------
function MI2_DecodeQualityOverview( mobInfo, mobData, mobIndex )
        if mobIndex then
                mobInfo = MobInfoDB[mobIndex]
        end

        if mobInfo.qi then
                local a,b,r1,r2,r3,r4,r5 = string.find( mobInfo.qi, "(%d*)/(%d*)/(%d*)/(%d*)/(%d*)")
                mobData.r1      = tonumber(r1)
                mobData.r2      = tonumber(r2)
                mobData.r3      = tonumber(r3)
                mobData.r4      = tonumber(r4)
                mobData.r5      = tonumber(r5)
        end
end -- MI2_DecodeQualityOverview


-----------------------------------------------------------------------------
-- MI2_DecodePlayerSpecificData()
--
-- Decode player specific data: number of kills, min damage, max damage, dps
-- Player specific data is encoded in mob record entries starting with
-- the lowercase letter "c" plus a player name index number, eg. "c7",
-- this is called the player ID code. The playerName parameter must give
-- the player ID code for the player data to decode.
-- The decoded data is stored in the given "mobData" structure.
-----------------------------------------------------------------------------
function MI2_DecodePlayerSpecificData( mobInfo, mobData, playerName, mobIndex )
        if mobIndex then
                mobInfo = MobInfoDB[mobIndex]
        end

        if mobInfo[playerName] then
                local a,b,kl,mind,maxd,dps = string.find( mobInfo[playerName], "(%d*)/(%d*)/(%d*)/(%d*)")
                mobData.kills           = tonumber(kl)
                mobData.minDamage       = tonumber(mind)
                mobData.maxDamage       = tonumber(maxd)
                mobData.dps                     = tonumber(dps)
        end
end


-----------------------------------------------------------------------------
-- MI2_DecodeItemList()
--
-- Decode the item list encoded in the "il" string of a mobInfo database
-- record. The result is stored in the given mobData record as a new
-- record field called "itemList".
-----------------------------------------------------------------------------
function MI2_DecodeItemList( mobInfo, mobData, mobIndex )
        if mobIndex then
                mobInfo = MobInfoDB[mobIndex]
        end

        if mobInfo.il then
                local lootItems = mobInfo.il
                local s,e, item, amount = string.find( lootItems, "(%d+)[:]?(%d*)" )
                if e then mobData.itemList = {} end
                while e do
                        mobData.itemList[tonumber(item)] = tonumber(amount) or 1
                        s,e, item, amount = string.find( lootItems, "/(%d+)[:]?(%d*)", e+1 )
                end
        end
end -- MI2_DecodeItemList()


-----------------------------------------------------------------------------
-- MI2_StoreMobData()
--
-- Store the contents of a given "mobData" structure (ie. the data describing
-- a mob) in the mob database. The "mobData" must be compatible to what is
-- returned by the "MI2_GetMobData()" function.
-----------------------------------------------------------------------------
function MI2x_StoreMobData( mobData, mobName, mobLevel, playerName, mobIndex )
        if not mobIndex then
                mobIndex = mobName..":"..mobLevel
        end

        -- create the mob basic info (".bi") string
        if mobData.mobType == 1 then mobData.mobType = "" end
        local basicInfo = (mobData.loots or "").."/"..(mobData.emptyLoots or "").."/"..(mobData.copper or "").."/"..(mobData.itemValue or "").."/"..
                     (mobData.clothCount or "").."/"..(mobData.xp or "").."/"..(mobData.mobType or "").."/"..(mobData.skinCount or "")

        -- create the mob quality info (".qi") string
        local qualityInfo = (mobData.r1 or "").."/"..(mobData.r2 or "").."/"..(mobData.r3 or "").."/"..(mobData.r4 or "").."/"..(mobData.r5 or "")

        -- create the mob player specific info, which is stored using the players name
        local playerInfo = (mobData.kills or "").."/"..(mobData.minDamage or "").."/"..(mobData.maxDamage or "").."/"..(mobData.dps or "")

        -- create the mob location data
        -- note: a copy of this code can be found in MI2_AdaptImportLocation()
        local loc = mobData.location or {}
        local locationInfo = (loc.x1 or "").."/"..(loc.y1 or "").."/"..(loc.x2 or "").."/"..(loc.y2 or "").."/"..(loc.c or "").."/"..(loc.z or "")

        -- create loot item list string for database
        local itemList = ""
        if mobData.itemList then
                local prefix = ""
                for itemID, amount in mobData.itemList do
                        itemList = itemList..prefix..itemID
                        if amount > 1 then
                                itemList = itemList..":"..amount
                        end
                        prefix = "/"
                end
        end
        
        -- only enter non empty data into database record
        local mobInfo = {}
        local recordNotEmpty = false
        if MobInfoConfig.SaveBasicInfo == 1 and basicInfo ~= "///////" then
                mobInfo.bi = basicInfo
                recordNotEmpty = true
        end
        if MobInfoConfig.SaveBasicInfo == 1 and qualityInfo ~= "////" then
                mobInfo.qi = qualityInfo
                recordNotEmpty = true
        end
        if MobInfoConfig.SaveCharData == 1 and playerInfo ~= "///" then
                mobInfo[playerName] = playerInfo
                recordNotEmpty = true
        end
        if MobInfoConfig.SaveItems == 1 and itemList ~= "" then
                mobInfo.il = itemList
                recordNotEmpty = true
        end

        if MobInfoConfig.SaveBasicInfo == 1 and locationInfo ~= "/////" then
                mobInfo.ml = locationInfo
                recordNotEmpty = true
        end

        -- do not store empty records in database
        if recordNotEmpty then
                MobInfoDB[mobIndex] = mobInfo
        end

end -- MI2_StoreMobData()


-----------------------------------------------------------------------------
-- MI2_RemoveCharData()
--
-- Remove all char specific data from the given Mob database record.
-----------------------------------------------------------------------------
function MI2_RemoveCharData( mobInfo )
        for entryName, entryData in mobInfo do
                if entryName ~= "bi" and entryName ~= "qi" and entryName ~= "il" and entryName ~= "ml" and entryName ~= "ver" then
                        mobInfo[entryName] = nil
                end
        end
end -- MI2_StoreMobData()


-----------------------------------------------------------------------------
-- MI2_PrepareForImport()
--
-- Prepare for importing external MobInfo databases into the main database.
-----------------------------------------------------------------------------
function MI2_PrepareForImport()
        local mobDbSize, healthDbSize, itemDbSize = 0, 0, 0

        --      external database version number check
        local version = MobInfoDB["DatabaseVersion:0"].ver
        if version and version < MI2_IMPORT_DB_VERSION then
                MI2_Import_Status = "BADVER"
                return
        end

        -- calculate Mob database size and import signature
        local levelSum, nameSum = 0, 0
        for index in MobInfoDB do  
                mobDbSize = mobDbSize + 1
                local mobName, mobLevel = MI2_GetIndexComponents( index )
                levelSum = levelSum + mobLevel
                nameSum = nameSum + string.len( mobName )
        end
        for index in MobHealthDB do  healthDbSize = healthDbSize + 1  end
        for index in MI2_ItemNameTable do  itemDbSize = itemDbSize + 1  end
        MI2_Import_Signature = mobDbSize.."_"..healthDbSize.."_"..itemDbSize.."_"..levelSum.."_"..nameSum

        -- store copy of databases to be imported and calculate import status
        MobInfoDB["DatabaseVersion:0"] = nil
        MobInfoDB_Import = MobInfoDB
        MI2_ItemNameTable_Import = MI2_ItemNameTable
        MI2_ZoneTable_Import = MI2_ZoneTable
        MobHealthDB_Import = MobHealthDB
        if mobDbSize > 1 then
                MI2_Import_Status = (mobDbSize-1).." Mobs"
        end
        if healthDbSize > 0 then
                if MI2_Import_Status then
                        MI2_Import_Status = MI2_Import_Status.." & " 
                end
                MI2_Import_Status = (MI2_Import_Status or "")..healthDbSize.." HP values"
        end

        MobInfoDB               = { ["DatabaseVersion:0"] = { ver = MI2_DB_VERSION } }
        MI2_CharTable   = { charCount = 0 }
        MI2_ZoneTable   = { cnt = 0 }
        MI2_ItemNameTable = {}
        MobHealthDB             = {     }
end -- MI2_PrepareForImport()
        
-----------------------------------------------------------------------------
-- MI2_DeleteMobData()
--
-- Delete data for a specific Mob from database and current target table.
-----------------------------------------------------------------------------
function MI2_DeleteMobData( mobIndex, deleteHealth )
        if mobIndex then
                MobInfoDB[mobIndex] = nil
                MI2_CurrentTargets[mobIndex] = nil
                if deleteHealth then
                        MobHealthDB[mobIndex] = nil
                end
                if mobIndex == MI2_Target.mobIndex then
                        MI2_Target = {}
                        MobHealth_Display()
                end
        end
end  -- MI2_DeleteMobData()


-----------------------------------------------------------------------------
-- chattext()
--
-- spits out msg to the chat channel. used in debuging
-----------------------------------------------------------------------------
function chattext(txt)
        if( DEFAULT_CHAT_FRAME ) then
                DEFAULT_CHAT_FRAME:AddMessage(txt)
        end
end -- chattext()


-----------------------------------------------------------------------------
-- MI2_InitOptions()
--
-- initialize MobInfo configuration options
-- this takes into account new options that have been added to MobInfo
-- in the course of developement
-----------------------------------------------------------------------------
 function MI2_InitOptions()
        -- initialize MobInfoConfig
        if not MobInfoConfig or not MobInfoConfig.ShowLoots then
                MobInfoConfig = { }
                MI2_SlashAction_Default()
        end

        -- initial defaults for all config options
        if  not MobInfoConfig.ShowBlankLines    then  MobInfoConfig.ShowBlankLines = 1  end
        if  not MobInfoConfig.TargetFontSize    then  MobInfoConfig.TargetFontSize = 10 end
        if  not MobInfoConfig.DisableMobInfo    then  MobInfoConfig.DisableMobInfo = 0  end
        if  not MobInfoConfig.ShowDamage        then  MobInfoConfig.ShowDamage = 1              end
        if  not MobInfoConfig.ShowMana          then  MobInfoConfig.ShowMana = 1                end
        if  not MobInfoConfig.ShowEmpty         then  MobInfoConfig.ShowEmpty = 0               end
        if  not MobInfoConfig.CombinedMode      then  MobInfoConfig.CombinedMode = 0    end
        if  not MobInfoConfig.ShowCombined      then  MobInfoConfig.ShowCombined = 1    end
        if  not MobInfoConfig.KeypressMode      then  MobInfoConfig.KeypressMode = 0    end
        if  not MobInfoConfig.StableMax         then  MobInfoConfig.StableMax = 0               end
        if  not MobInfoConfig.TargetHealth      then  MobInfoConfig.TargetHealth = 1    end
        if  not MobInfoConfig.TargetMana        then  MobInfoConfig.TargetMana = 1              end
        if  not MobInfoConfig.HealthPercent     then  MobInfoConfig.HealthPercent = 1   end
        if  not MobInfoConfig.ManaPercent       then  MobInfoConfig.ManaPercent = 1             end
        if  not MobInfoConfig.HealthPosX        then  MobInfoConfig.HealthPosX = -7             end
        if  not MobInfoConfig.HealthPosY        then  MobInfoConfig.HealthPosY = 11             end
        if  not MobInfoConfig.ManaPosX          then  MobInfoConfig.ManaPosX = -7               end
        if  not MobInfoConfig.ManaPosY          then  MobInfoConfig.ManaPosY = 11               end
        if  not MobInfoConfig.TargetFont        then  MobInfoConfig.TargetFont = 2              end
        if  not MobInfoConfig.SavePlayerHp      then  MobInfoConfig.SavePlayerHp = 0    end
        if  not MobInfoConfig.CompactMode       then  MobInfoConfig.CompactMode = 1             end
        if  not MobInfoConfig.ShowItems         then  MobInfoConfig.ShowItems = 1               end
        if  not MobInfoConfig.SaveItems         then  MobInfoConfig.SaveItems = 1               end
        if  not MobInfoConfig.SaveCharData      then  MobInfoConfig.SaveCharData = 1    end
        if  not MobInfoConfig.ItemsQuality      then  MobInfoConfig.ItemsQuality = 2    end
        if  not MobInfoConfig.SaveBasicInfo     then  MobInfoConfig.SaveBasicInfo = 1   end
        if  not MobInfoConfig.ItemTooltip       then  MobInfoConfig.ItemTooltip = 1             end
        if  not MobInfoConfig.ItemFilter        then  MobInfoConfig.ItemFilter = ""             end
        if  not MobInfoConfig.ShowLocation      then  MobInfoConfig.ShowLocation = 1    end
        if  not MobInfoConfig.SaveLocation      then  MobInfoConfig.SaveLocation = 1    end
        if  not MobInfoConfig.ShowClothSkin     then  MobInfoConfig.ShowClothSkin = 1   end
        if  not MobInfoConfig.ImportOnlyNew     then  MobInfoConfig.ImportOnlyNew = 0   end

        -- former option "HealthOff" has been renamed to "DisableHealth"
        if  not MobInfoConfig.DisableHealth  then  
                MobInfoConfig.DisableHealth = (MobInfoConfig.HealthOff or 0)
        end

        -- config values that no longer exist
        if  MobInfoConfig.HealthOff             then    MobInfoConfig.HealthOff = nil  end
        if  MobInfoConfig.ManaDistance  then    MobInfoConfig.ManaDistance = nil end
        if  MobInfoConfig.ShowPercent   then    MobInfoConfig.ShowPercent = nil end
        if  MobInfoConfig.CustomTracks  then    MobInfoConfig.CustomTracks = nil end
        if  MobInfoConfig.SaveAllValues then    MobInfoConfig.SaveAllValues = nil end
        if  MobInfoConfig.MobDbVersion  then    MobInfoConfig.MobDbVersion = nil end
        if  MobInfoConfig.MobDbVersion  then    MobInfoConfig.MobDbVersion = nil end
        if  MobInfoConfig.ClearOnExit   then    MobInfoConfig.ClearOnExit = nil end
        if  MobInfoConfig.SaveGoodItems then    MobInfoConfig.SaveGoodItems = nil       end
        if  MobInfoConfig.SaveQualityData       then    MobInfoConfig.SaveQualityData = nil     end
end -- MI2_InitOptions()


-----------------------------------------------------------------------------
-- MI2_IndexComponents()
--
-- Return the component parts of a mob index: mob name, mob level
-----------------------------------------------------------------------------
function MI2_GetIndexComponents( mobIndex )
        local a, b, mobName, mobLevel = string.find(mobIndex, "(.+):(.+)$")
        mobLevel = tonumber(mobLevel)
        return mobName, mobLevel
end  -- MI2_IndexComponents()


-----------------------------------------------------------------------------
-- MI2_CleanupDatabases()
--
-- Cleanup for MobInfo database. This function corrects bugs in the
-- MobInfo database and applies some changes that have been made to
-- the format of the actual database entires.
--
-- With "DatabaseVersion" 3 the database storage format has changed completely,
-- which means that a complex conversion must be applied to convert the
-- old into the new database format.
--
-- increased DB version to 4 to enforce a cleanup run for everyone installing
-- the newest MobInfo release (2.64 and above)
-----------------------------------------------------------------------------
function MI2_CleanupDatabases()
--      local startTime = GetTime()
        local mobIndex, mobInfo

        if MobInfoDB.DatabaseVersion then MobInfoDB.DatabaseVersion = nil end

        -- attempt to automatically fix invalid database entries where the index is bugged
        for mobIndex, mobInfo in MobInfoDB do
                local mobName, mobLevel = MI2_GetIndexComponents( mobIndex )
                if not mobName or not mobLevel or mobName == "" then
                        MobInfoDB[mobIndex] = nil
                end
        end

        -- update database to the most recent version
        -- this will convert old databases into the new DB format and will attempt
        -- to fix any invalid database entries
        if not MobInfoDB["DatabaseVersion:0"] or MobInfoDB["DatabaseVersion:0"].ver < MI2_DB_VERSION then
                if MI2_Debug > 0 then chattext( "M2DBG: running DB cleanup for ver=[nil]" ) end
                MobInfoDB["DatabaseVersion:0"] = nil

                -- loop through all Mobs in the database
                for mobIndex, mobInfo in MobInfoDB do
                        -- build new "basic info" entry from old separate entries
                        if (mobInfo.lt or mobInfo.el or mobInfo.cp or mobInfo.iv or mobInfo.cc or mobInfo.xp) and not mobInfo.bi then
                                if mobInfo.lt and mobInfo.lt <= 0 then mobInfo.lt = nil end
                                if mobInfo.cp and mobInfo.cp <= 0 then mobInfo.cp = nil end
                                if mobInfo.iv and mobInfo.iv <= 0 then mobInfo.iv = nil end
                                if mobInfo.el and mobInfo.el <= 0 then mobInfo.el = nil end
                                if mobInfo.cc and mobInfo.cc <= 0 then mobInfo.cc = nil end
                                mobInfo.bi = (mobInfo.lt or "").."/"..(mobInfo.el or "").."/"..(mobInfo.cp or "").."/"..(mobInfo.iv or "").."/"..(mobInfo.cc or "").."/"..(mobInfo.xp or "").."/"..(mobInfo.mt or "")
                        end
                        local s, slashCount = string.gsub( (mobInfo.bi or ""), "/", "@" )
                        if slashCount == 6 then mobInfo.bi = mobInfo.bi.."/"; slashCount = slashCount + 1 end
                        if mobInfo.bi == "///////" then mobInfo.bi = nil end
                        if mobInfo.bi and slashCount ~= 7 then mobInfo.bi = nil end

                        -- build new "quality info" entry from  old separate entries
                        if (mobInfo.r0 or mobInfo.r1 or mobInfo.r2 or mobInfo.r3 or mobInfo.r4) and not mobInfo.qi then
                                if mobInfo.r0 and mobInfo.r0 <= 0 then mobInfo.r0 = nil end
                                if mobInfo.r1 and mobInfo.r1 <= 0 then mobInfo.r1 = nil end
                                if mobInfo.r2 and mobInfo.r2 <= 0 then mobInfo.r2 = nil end
                                if mobInfo.r3 and mobInfo.r3 <= 0 then mobInfo.r3 = nil end
                                if mobInfo.r4 and mobInfo.r4 <= 0 then mobInfo.r4 = nil end
                                mobInfo.qi = (mobInfo.r0 or "").."/"..(mobInfo.r1 or "").."/"..(mobInfo.r2 or "").."/"..(mobInfo.r3 or "").."/"..(mobInfo.r4 or "")
                        end
                        if mobInfo.qi == "////" then  mobInfo.qi = nil  end

                        -- loop through all Mob database record entries
                        -- process char specific data and remove all invalid entries from database record
                        for entryName, entryData in mobInfo do
                                if type(entryData) == "table" then
                                        -- char specific data in table form found: convert it to new DB format
                                        local dl = entryData.dl
                                        local du = entryData.du
                                        local dd = entryData.dd
                                        if (dl or du) and not dd then
                                                dd = dl.."/"..du.."/"..0
                                        end
                                        mobInfo[entryName] = (entryData.kl or "").."/"..(dd or "")
                                else
                                        local isCharEntry = type(entryData) == "string" and (string.find(entryName,":") ~= nil or MI2_CharTable[entryName]) and string.find(entryData,"/") ~= nil
                                        isCharEntry = isCharEntry or type(entryData) == "string"
                                        if isCharEntry then
                                                if mobInfo[entryName] == "///" then
                                                        mobInfo[entryName] = nil
                                                end
                                        elseif entryName ~= "bi" and entryName ~= "qi" and entryName ~= "il" and entryName ~= "ml" then
                                                mobInfo[entryName] = nil
                                        end
                                end
                        end -- for
                end -- for

                -- loop through all Mobs in the database and convert char name into char index
                -- delete all empty mob records
                for mobIndex, mobInfo in MobInfoDB do
                        local entryCount = 0
                        for entryName, entryData in mobInfo do
                                entryCount = entryCount + 1
                                local isCharEntry = type(entryData) == "string" and string.find(entryName,":") ~= nil and string.find(entryData,"/") ~= nil
                                if isCharEntry then
                                        if not MI2_CharTable[entryName] then
                                                MI2_CharTable.charCount = MI2_CharTable.charCount + 1
                                                MI2_CharTable[entryName] = "c"..MI2_CharTable.charCount
                                        end
                                        mobInfo[MI2_CharTable[entryName]] = entryData
                                        mobInfo[entryName] = nil
                                end
                        end -- for
                        if entryCount == 0 then
                                MobInfoDB[mobIndex] = nil
                        end
                end

                MobInfoDB["DatabaseVersion:0"] = { ver = MI2_DB_VERSION }
        end

--      chattext( "<MobInfo> database conversion time = "..(GetTime()-startTime).." seconds" )
end  -- MI2_CleanupDatabases()


-----------------------------------------------------------------------------
-- MI2_ImportLocationsFromMI2B()
--
-- Import the Mob locations that have been recorded by the MI2_Browser
-- AddOn into the MobInfo2 Mob database. Only import correct location
-- data for Mobs that do not yet have a location.
-----------------------------------------------------------------------------
function MI2_ImportLocationsFromMI2B()
        -- import TipBuddy Mob location data into the MobInfo database
        if MobInfoDB_B and not MobInfoDB_B.converted then
                for idx, val in MobInfoDB_B do
                        if MobInfoDB[idx] and not MobInfoDB[idx].ml and val.loc and val.loc.l and val.loc.x and val.loc.y then
                                local x = floor( val.loc.x * 100.0 )
                                local y = floor( val.loc.y * 100.0 )
                                local _, _, continent, zone = string.find( (tostring(val.loc.l)), MI2B_LOCPATTERN )
                                if continent and zone and x > 0 and y > 0 then
                                        local locationInfo = (x or "").."/"..(y or "").."/"..(x or "").."/"..(y or "").."/"..(continent or "").."/"..(zone or "")
                                        MobInfoDB[idx].ml = locationInfo
                                end
                        end
                end
                MobInfoDB_B.converted = 1
        end
end

-----------------------------------------------------------------------------
-- MI2_AddItemToXRefTable()
--
-- build the cross reference table for fast item lookup
-- The table is indexed by item name and lists all Mobs that drop the item
-----------------------------------------------------------------------------
local function MI2_AddItemToXRefTable( mobIndex, itemName, itemAmount )
        if not MI2_XRefItemTable[itemName] then
                MI2_XRefItemTable[itemName] = {}
        end

        local oldAmount = MI2_XRefItemTable[itemName][mobIndex]
        MI2_XRefItemTable[itemName][mobIndex] = (oldAmount or 0) + itemAmount

--chattext("DBG: XRefItemTable: item=["..itemName.."], mob=["..mobIndex.."], val="..MI2_XRefItemTable[itemName][mobIndex] )
end -- MI2_AddItemToXRefTable()


-----------------------------------------------------------------------------
-- MI2_BuildXRefItemTable()
--
-- build the cross reference table for fast item lookup
-- The table is indexed by item name and lists all Mobs that drop the item.
-- It is needed for quickly generating the "Dropped By" list in item tooltips.
-----------------------------------------------------------------------------
function MI2_BuildXRefItemTable()
        MI2_XRefItemTable = {}
        for mobIndex, mobInfo in MobInfoDB do
                local mobData = {}
                MI2_DecodeItemList( mobInfo, mobData )
                if mobData.itemList then 
                        for itemID, amount in mobData.itemList do
                                local itemText = MI2_ItemNameTable[itemID]
                                if itemText then
                                        itemText = string.sub( itemText, 1, -3 )
                                        MI2_AddItemToXRefTable( mobIndex, itemText, amount )
                                end
                        end
                end
        end
end -- MI2_BuildXRefItemTable()


-----------------------------------------------------------------------------
-- MI2_NewMobTarget()
--
-- Add a Mob to the list of current targets. MobInfo tracks all current
-- targets to collect data on the Mobs and for advanced kill counting.
-----------------------------------------------------------------------------
function MI2_NewMobTarget( index )
        if  not MI2_CurrentTargets[index] then
                MI2_CurrentTargets[index] = {}
        end  

        local mobData = MI2_CurrentTargets[index]
        mobData.time = GetTime()
        mobData.killed = nil

        -- obtain and store mob type 
        local mobType  = UnitClassification( "target" )
        if mobType and mobType ~= "normal" then
                if mobType == "rare" or mobType == "elite" then
                        mobData.mobType = 2
                else
                        mobData.mobType = 3
                end
        end

        return mobData
end -- MI2_NewMobTarget()


-----------------------------------------------------------------------------
-- MI2_RecordDamage()
--
-- record damage value for a mob
-----------------------------------------------------------------------------
function MI2_RecordDamage( index, damage )
        local mobData = MI2_CurrentTargets[index]

        if MI2_Debug > 1 then chattext( "M2DBG: damage reported: mob=["..index.."], dmg="..damage ) end

        -- update minimum and/or maximum damage for mob
        if mobData and damage > 0 then
                if not mobData.minDamage or mobData.minDamage <= 0 then
                        mobData.minDamage, mobData.maxDamage = damage, damage
                elseif damage < mobData.minDamage then
                        if MI2_Debug > 0 then chattext( "M2DBG: recording new MIN dmg "..damage.." for ["..index.."] (old="..mobData.minDamage..")" ) end
                        mobData.minDamage = damage
                elseif damage > mobData.maxDamage then
                        if MI2_Debug > 0 then chattext( "M2DBG: recording new MAX dmg "..damage.." for ["..index.."] (old="..mobData.maxDamage..")" ) end
                        mobData.maxDamage = damage
                end
        end
end -- MI2_RecordDamage()


-----------------------------------------------------------------------------
-- MI2_RecordDps()
--
-- record a new dps (damage per second) value for a specific mob
-- dps gets calculated from damage done within a given time
-----------------------------------------------------------------------------
function MI2_RecordDps( index, deltaTime, damage  )
        local mobData = MI2_CurrentTargets[index]

        -- only store dps for fights longer then 4 seconds
        if mobData and deltaTime > 4 then
                -- calculate DPS value
                local newDps = damage / deltaTime
                if not mobData.dps then mobData.dps = newDps end
                mobData.dps = floor( ((2.0 * mobData.dps) + newDps) / 3.0 )

                -- update the dd (damage data) entry for this mob
                if MI2_Debug > 0 then chattext( "M2DBG: recording new dps: idx="..index..", new dps="..mobData.dps ) end
        end
end -- MI2_RecordDps()


-----------------------------------------------------------------------------
-- MI2_RecordKill()
--
-- record a kill and optionally the xp you got for the kill for the given mob
-----------------------------------------------------------------------------
local function MI2_RecordKill( index, xp )
        local mobData = MI2_CurrentTargets[index]

        if mobData then
                if not mobData.killed then
                        mobData.kills = (mobData.kills or 0) + 1
                end
                mobData.killed = 1
                if xp > 0 then
                        mobData.xp = xp
                end
                mobData.time = GetTime()
        end

        if MI2_Debug > 0 then chattext( "M2DBG: recording kill "..(mobData.kills or "<nil>").." and XP "..xp.." for mob ["..index.."]" ) end
end -- MI2_RecordKill()


-----------------------------------------------------------------------------
-- MI2_RecordLocation()
--
-- record the current location of the player as the location of the Mob
-- he is fighting
-----------------------------------------------------------------------------
local function MI2_RecordLocation( index )
        local mobData = MI2_CurrentTargets[index]

        if mobData and not mobData.location then
                local x, y = GetPlayerMapPosition("player")
                x = floor( x * 100.0 )
                y = floor( y * 100.0 )
                mobData.location = { x1=x, x2=x, y1=y, y2=y, c=MI2_CurContinent, z=MI2_CurZone }
        end
end -- MI2_RecordLocation()


-----------------------------------------------------------------------------
-- MI2_RecordLootData()
--
-- Record the data for one loot item. This function is called in turn for
-- each loot item in the loot window.
-----------------------------------------------------------------------------
local function MI2_RecordLootData( mobData, itemID, money, itemValue, quality, isSkinningLoot )
        mobData.clothCount = (mobData.clothCount or 0) + (miClothLoot[itemID] or 0 )
        mobData.copper = (mobData.copper or 0) + money
        if isSkinningLoot then
                mobData.skinCount = (mobData.skinCount or 0) + 1
        else
                -- count item value only for non skinning loot
                mobData.itemValue = (mobData.itemValue or 0) + itemValue
        end

        -- decide whether item should be counted in quality overview
        if itemValue < 1 and quality == 2 or isSkinningLoot then
                quality = -1
        end

        -- record loot item quality (if enabled)
        if quality == 1 then 
                mobData.r1 = (mobData.r1 or 0) + 1
        elseif quality == 2 then
                mobData.r2 = (mobData.r2 or 0) + 1
        elseif quality == 3 then
                mobData.r3 = (mobData.r3 or 0) + 1
        elseif quality == 4 then
                mobData.r4 = (mobData.r4 or 0) + 1
        elseif quality == 5 then
                mobData.r5 = (mobData.r5 or 0) + 1
        end
end -- MI2_RecordLootData()


-----------------------------------------------------------------------------
-- MI2_AddTwoMobs()
--
-- add the data for two mobs,
-- the data of the second mob (mobData2) is added to the data of the first
-- mob (mobData1). The result is returned in "mobData1".
-----------------------------------------------------------------------------
function MI2_AddTwoMobs( mobData1, mobData2 )
        mobData1.loots = (mobData1.loots or 0) + (mobData2.loots or 0)
        mobData1.kills = (mobData1.kills or 0) + (mobData2.kills or 0)
        mobData1.emptyLoots = (mobData1.emptyLoots or 0) + (mobData2.emptyLoots or 0)
        mobData1.clothCount = (mobData1.clothCount or 0) + (mobData2.clothCount or 0)
        mobData1.copper = (mobData1.copper or 0) + (mobData2.copper or 0)
        mobData1.itemValue = (mobData1.itemValue or 0) + (mobData2.itemValue or 0)
        mobData1.skinCount = (mobData1.skinCount or 0) + (mobData2.skinCount or 0)
        mobData1.r1 = (mobData1.r1 or 0) + (mobData2.r1 or 0)
        mobData1.r2 = (mobData1.r2 or 0) + (mobData2.r2 or 0)
        mobData1.r3 = (mobData1.r3 or 0) + (mobData2.r3 or 0)
        mobData1.r4 = (mobData1.r4 or 0) + (mobData2.r4 or 0)
        mobData1.r5 = (mobData1.r5 or 0) + (mobData2.r5 or 0)
        if mobData2.mobType then mobData1.mobType = mobData2.mobType end
        if mobData2.xp then mobData1.xp = mobData2.xp end

        -- combine locations
        if mobData1.location or mobData2.location then
                if not mobData1.location then
                        mobData1.location = mobData2.location
                elseif mobData2.location then
                        if mobData2.location.x1 < mobData1.location.x1 then
                                mobData1.location.x1 = mobData2.location.x1
                        end
                        if mobData2.location.x2 > mobData1.location.x2 then
                                mobData1.location.x2 = mobData2.location.x2
                        end
                        if mobData2.location.y1 < mobData1.location.y1 then
                                mobData1.location.y1 = mobData2.location.y1
                        end
                        if mobData2.location.y2 > mobData1.location.y2 then
                                mobData1.location.y2 = mobData2.location.y2
                        end
                        if mobData1.location.c == 0 then
                                mobData1.location.c = mobData2.location.c
                        end
                end
        end

        -- combine DPS od two mobs
        if not mobData1.dps then
                mobData1.dps = mobData2.dps
        else
                if mobData2.dps then
                        mobData1.dps = floor( ((2.0 * mobData1.dps) + mobData2.dps) / 3.0 )
                end
        end

        -- combine minimum and maximum damage   
        if (mobData2.minDamage or 99999) < (mobData1.minDamage or 99999) then
                mobData1.minDamage = mobData2.minDamage
        end
        if (mobData2.maxDamage or 0) > (mobData1.maxDamage or 0) then
                mobData1.maxDamage = mobData2.maxDamage
        end
        
        -- add loot item tables of the two mobs
        if mobData2.itemList then
                if not mobData1.itemList then mobData1.itemList = {} end
                for itemID, amount in mobData2.itemList do
                        mobData1.itemList[itemID] = (mobData1.itemList[itemID] or 0) + mobData2.itemList[itemID]
                end
        end

        if mobData1.loots == 0 then mobData1.loots = nil end
        if mobData1.kills == 0 then mobData1.kills = nil end
        if mobData1.emptyLoots == 0 then mobData1.emptyLoots = nil end
        if mobData1.clothCount == 0 then mobData1.clothCount = nil end
        if mobData1.copper == 0 then mobData1.copper = nil end
        if mobData1.itemValue == 0 then mobData1.itemValue = nil end
        if mobData1.skinCount == 0 then mobData1.skinCount = nil end
        if mobData1.dps == 0 then mobData1.dps = nil end
        if mobData1.r1 == 0 then mobData1.r1 = nil end
        if mobData1.r2 == 0 then mobData1.r2 = nil end
        if mobData1.r3 == 0 then mobData1.r3 = nil end
        if mobData1.r4 == 0 then mobData1.r4 = nil end
        if mobData1.r5 == 0 then mobData1.r5 = nil end
end  -- MI2_AddTwoMobs


-----------------------------------------------------------------------------
-- MI2_ProcessTargetTable()
--
-- process the MobInfo target table
-- The target table collects all mob related data (except for health) during
-- a fight and when looting. This function transfers the data that has been
-- collected into the main mob database.
--
-- There are 2 criterias for detecting when the right time has come to
-- transfer a mob into the database. After storing a mob in the database
-- it is removed from the table of current targets:
--   * Looted : mobs that have been looted have been fully processed, their
--     data is complete and can thus be stored
--   * Timeout : mobs that have been in the table for over 20 seconds
--     and have not been killed in that time can be stored
--   * Timeout : mobs that have been killed and have not been looted within
--     the last 60 seconds
-- 
-----------------------------------------------------------------------------
function MI2_ProcessTargetTable()
        for index, newMobData in MI2_CurrentTargets do
                local deltaT = GetTime() - newMobData.time
                if (newMobData.loots and newMobData.loots == newMobData.kills)
                                or (not newMobData.loots and newMobData.skinCount)
                                or (not newMobData.kills and deltaT > 30)
                                or deltaT > 60 then
                        if MI2_Debug > 1 then chattext( "M2DBG: entering mob ["..index.."] into database" ) end
                        local mobName, mobLevel = MI2_GetIndexComponents( index )
                        
                        local realMobData = MI2_GetMobData( mobName, mobLevel )
                        MI2_AddTwoMobs( realMobData, newMobData )
                        MI2x_StoreMobData( realMobData, mobName, mobLevel, MI2_PlayerName )
                        MI2_CurrentTargets[index] = nil
                end
        end
end -- MI2_ProcessTargetTable


-----------------------------------------------------------------------------
-- MI2_GetMobHealthStr()
--
-- Returns the mobhealth in the form of xx/xx from the mobdb formed by
-- MobHealth mod Pulled from Telo's MobHealth
-----------------------------------------------------------------------------
local function MI2_GetMobHealthStr( index, healthPercent )
        local ppp = MobHealth_PPP( index )
        if ppp > 0 and healthPercent then
                return string.format("%d / %d", (healthPercent * ppp) + 0.5, (100 * ppp) + 0.5)
        end
end -- MI2_GetMobHealthStr()


-----------------------------------------------------------------------------
-- copper2text()
--
-- Turns a full copper amount to a readable string, eg. 10340 = 1g 3s 40c
-----------------------------------------------------------------------------
function copper2text(copper)
        local g,s,c
                
        g = floor(copper / COPPER_PER_GOLD)
        s = floor(copper / COPPER_PER_SILVER) - g * SILVER_PER_GOLD
        c = copper - g * COPPER_PER_GOLD - s * COPPER_PER_SILVER

        if g > 0 then  
                return mifontWhite..g..mifontYellow..'g '..mifontWhite..s ..mifontSubWhite..'s '..mifontWhite..c..mifontGold..'c'
        end  

        if s > 0 then  
                return mifontWhite..s ..mifontSubWhite..'s '..mifontWhite..c..mifontGold..'c'
        end  

        return mifontWhite..c..mifontGold..'c'
end


-----------------------------------------------------------------------------
-- lootName2Copper()
--
-- Turns a lootname like 1 Gold 3 Silver 40 Copper to total copper 10340
-----------------------------------------------------------------------------
function lootName2Copper(item)
        local i = 0
        local g,s,c = 0
        local money = 0
          
        i = string.find(item, MI_TXT_GOLD )
        if i then
                g = tonumber( string.sub(item,0,i-1) )
                item = string.sub(item,i+5,string.len(item))
                money = money + ((g or 0) * COPPER_PER_GOLD)
        end
        i = string.find(item, MI_TXT_SILVER )
        if i then
                s = tonumber( string.sub(item,0,i-1) )
                item = string.sub(item,i+7,string.len(item))
                money = money + ((s or 0) * COPPER_PER_SILVER)
        end
        i = string.find(item, MI_TXT_COPPER )
        if i then
                c = tonumber( string.sub(item,0,i-1) )
                money = money + (c or 0)
        end

        return money
end -- lootName2Copper()


-----------------------------------------------------------------------------
-- MI2_FindItemValue()
--
-- Find the item value in either the Auctioneer database or in out own copy
-- of the Auctioneer item value database or by asking KC_Items
-----------------------------------------------------------------------------
function MI2_FindItemValue( itemID, link )
        local price
        
        -- check if KC_Items is available and knows the price
        if KC_Items then
                if KC_Items.GetItemPrices and KC_Items.GetCode and link then
                        price = KC_Items:GetItemPrices( KC_Items:GetCode(link) )
                elseif KC_Common.GetItemPrices then
                        price = KC_Common:GetItemPrices( itemID )
                end
                if price and price > 0 then return price end
        end
        
        -- check if ItemsSync is installed and knows the price
        if ISync and ISync.FetchDB then
                price = tonumber( ISync:FetchDB(itemID, "price") or 0 )
                if price and price > 0 then return price end
        end

        -- check if Auctioneer is installed and knows the price
        if  Auctioneer_GetVendorBuyPrice  then
                price = Auctioneer_GetVendorSellPrice(itemID)
                if price and price > 0 then return price end
        end

        -- check if built-in copy of the Auctioneer base prices knows the item price
        if MI2_BasePrices[itemID] then
                return MI2_BasePrices[itemID]
        end

        return 0  
end -- MI2_FindItemValue()


-----------------------------------------------------------------------------
-- GetLootId()
--
-- get loot ID code for given loot slot number, also return link object
-----------------------------------------------------------------------------
local function GetLootId( slot )
        local idNumber = 0

        local link = GetLootSlotLink( slot )
        if link then
                local _, _, idCode = string.find(link, "|Hitem:(%d+):(%d+):(%d+):")
                idNumber = tonumber( idCode or 0 )
        end

        return idNumber, link
end -- GetLootId()


-----------------------------------------------------------------------------
-- MI2_RecordAllLootItems()
--
-- Record the data for all items found in the currently open loot window.
-- Return to the caller whether this loot window represents real mob loot
-- or not. Examples for "not" are: skinning, clam loot
-----------------------------------------------------------------------------
local function MI2_RecordAllLootItems( mobIndex, mobData )
        local isSkinningLoot = false

        -- iterate through all loot slots and record data for each item
        for slot = 1, GetNumLootItems(), 1 do
                local money, itemValue = 0, 0

                -- obtain loot slot data from WoW
                local texture, itemName, quantity, quality = GetLootSlotInfo( slot )
                local itemID, link = GetLootId( slot )
                quality = quality + 1

                -- abort loot processing upon finding clam meat (ie. a clam was opened)
                if string.find(itemName, MI_TXT_CLAM_MEAT) ~= nil then  return true  end

                -- calculate value of money loot
                if LootSlotIsCoin(slot) then
                        money = lootName2Copper(itemName)
                        quality = -1
                elseif LootSlotIsItem(slot) then
                        itemValue = MI2_FindItemValue( itemID, link )
                end

                -- skinning loot => its a skinning loot window
                if miSkinLoot[itemID] and slot == 1 then  
                        isSkinningLoot = true  
                end

                -- record item data within Mob database and in global item table
                -- update cross reference table accordingly
                if MobInfoConfig.SaveItems == 1 and quality >= MobInfoConfig.ItemsQuality then
                        if not mobData.itemList then mobData.itemList = {} end
                        mobData.itemList[itemID] = (mobData.itemList[itemID] or 0) + quantity
                        MI2_ItemNameTable[itemID] = itemName.."/"..quality
                        MI2_AddItemToXRefTable( mobIndex, itemName, mobData.itemList[itemID] )
                end

                -- add loot item data to MobInfoDB
                MI2_RecordLootData( mobData, itemID, money, itemValue, quality, isSkinningLoot )
                if MI2_Debug > 1 then chattext( "M2DBG: Loot: slot="..slot..", name=["..item.."], id=["..itemID.."], val=["..itemValue.."], q=["..(quality+1).."]" ) end
        end -- for loop

        return isSkinningLoot;
end -- MI2_RecordAllLootItems()


-----------------------------------------------------------------------------
-- MI2_RecordLooting()
--
-- for non skinning loot increment loot counter and (if applicable)
-- update kill counter, if there have been more kills then loots
-----------------------------------------------------------------------------
local function MI2_RecordLooting( mobData, numLootItems )
        mobData.loots = (mobData.loots or 0) + 1
        if mobData.loots > (mobData.kills or 0) then
                mobData.kills = (mobData.kills or 0) + 1
        end
        -- update empty loot counter
        if numLootItems < 1 then
                mobData.emptyLoots = (mobData.emptyLoots or 0) + 1
        end
end


-----------------------------------------------------------------------------
-- MI2_GetCorpseId()
--
-- create a (hopefully) unique corpse ID out of the loot items found in 
-- the corpse loot window, return nil if loot is empty
-- WoW Bug: GetNumLootItems() includes emptied loot window slots
-----------------------------------------------------------------------------
local function MI2_GetCorpseId( index )
        local corpseId
        local numSlots = GetNumLootItems()
        local numItems = 0 

        if index and numSlots > 0 then
                corpseId = index
                for slot = 1, numSlots do
                        local texture, item = GetLootSlotInfo( slot )
                        if item ~= "" then corpseId = corpseId..item end
                end
        end

        return corpseId
end -- MI2_GetCorpseId()


-----------------------------------------------------------------------------
-- MI2_StoreCorpseId()
--
-- enter given corpse ID into list of all corpse IDs
-- a list of corpse IDs is maintained to allow detecting corpse reopening
-----------------------------------------------------------------------------
local function MI2_StoreCorpseId( corpseId, isNewCorpse )
        if MI2_Debug > 0 then chattext( "M2DBG: storing new corpse ID ["..(corpseId or "nil").."], newIdx="..MI2_NewCorpseIdx..", curIdx="..(MI2_CurrentCorpseIndex or "<nil>") ) end

        -- store a new corpse ID
        if isNewCorpse then
                MI2_NewCorpseIdx = MI2_NewCorpseIdx + 1
                if MI2_NewCorpseIdx > 10 then
                        MI2_NewCorpseIdx = 1
                end
                MI2_CurrentCorpseIndex = MI2_NewCorpseIdx
        end

        if MI2_CurrentCorpseIndex then
                MI2_RecentCorpses[MI2_CurrentCorpseIndex] = corpseId
                if not corpseId then
                        MI2_CurrentCorpseIndex = nil
                end
        end
end -- MI2_StoreCorpseId()


-----------------------------------------------------------------------------
-- MI2_CheckForCorpseReopen()
--
-- Check if the corpse for the given mob index is being reopened.
-- This is done by calculating a (hopefully) unique corpse ID and adding
-- it to the list if it is a new corpse ID. 
-----------------------------------------------------------------------------
local function MI2_CheckForCorpseReopen( mobIndex )
        local isReopen = false
        local corpseId = MI2_GetCorpseId( mobIndex )

        -- check if corpse ID is already in the list
        for index, recentCorpseId in MI2_RecentCorpses do
                if recentCorpseId == corpseId then
                        MI2_CurrentCorpseIndex = index
                        isReopen = true
                        break
                end
        end

        -- add corpse ID the the list if it is a new one
        if corpseId and not isReopen then
                MI2_StoreCorpseId( corpseId, 1 )
        end

        return isReopen
end -- MI2_CheckForCorpseReopen()


-----------------------------------------------------------------------------
-- MI2_EventLootOpened()
--
-- WoW event notification that loot frame has been opened
-----------------------------------------------------------------------------
function MI2_EventLootOpened( )
        local index = MI2_Target.mobIndex or MI2_LastTargetIdx
        local mobData = MI2_CurrentTargets[index]
        local numLootItems = GetNumLootItems()

        MI2_CurrentCorpseIndex = nil
        MI2_LootFrameOpen = true

        -- if there is a target it must be a dead one, the loot must be mob loot
        -- reject non empty loots without target (empty loots opened by "QuickLoot" have no target)
        if not mobData or (not MI2_Target.mobIndex and numLootItems > 0)
                        or (MI2_Target.mobIndex and not UnitIsDead("target")) or MI2_IsNonMobLoot then
                if MI2_Debug > 0 then chattext( "M2DBG: non Mob loot detected, nonMobFlag="..tostring(MI2_IsNonMobLoot) ) end
                MI2_IsNonMobLoot = false
                return
        end

        -- check if this is a known corpse being reopened, reopened corpses
        -- can (and must) be ignored because they have already been fully processed
        if MI2_CheckForCorpseReopen(index) then
                if MI2_Debug > 0 then chattext( "M2DBG: corpse REOPEN detected" ) end
                return
        end     

        -- record all loot found on the corpse (called each time to catch skinning))
        -- record location where Mob has been looted
        local skinningLoot = MI2_RecordAllLootItems( index, mobData )
        MI2_RecordLocation( index )
        if not skinningLoot then
                MI2_RecordLooting( mobData, numLootItems )
        end

        -- process target data right away if loots and kills are balanced
        if mobData.loots == mobData.kills or skinningLoot then
                MI2_ProcessTargetTable()
        end
end -- MI2_EventLootOpened()


-----------------------------------------------------------------------------
-- MI2_EventLootSlotCleared()
--
-- WoW event notification that one loot item has been looted.
-- This results in a new corpse ID which must be stored for corpse reopen
-- detection
-----------------------------------------------------------------------------
function MI2_EventLootSlotCleared( )
        if MI2_CurrentCorpseIndex then
                MI2_StoreCorpseId( MI2_GetCorpseId(MI2_Target.mobIndex) )
        end
end -- MI2_EventLootSlotCleared
-----------------------------------------------------------------------------


-----------------------------------------------------------------------------
-- MI2_EventLootClosed()
--
-- Event handler for WoW event that the loot window has been closed.
-- This is used to catch empty loots when using auto-loot (Shift+RightClick)
-- In this case "LOOT_CLOSED" is the only loot event that fires
-----------------------------------------------------------------------------
function MI2_EventLootClosed( )
        local mobIndex = MI2_Target.mobIndex
        if mobIndex and not MI2_LootFrameOpen then
                local mobData = MI2_NewMobTarget( mobIndex )
                MI2_RecordLooting( mobData, 0 )
                MI2_ProcessTargetTable()
        end
        MI2_LootFrameOpen = false
end -- MI2_EventLootClosed


-----------------------------------------------------------------------------
-- MI2_GetLootItemString()
--
-- Get and return a string describing a specific loot item.
-- The loot item is identified by its item ID.
-- The color for the string is returned as well
-----------------------------------------------------------------------------
function MI2_GetLootItemString( itemID )
        local itemString = MI2_ItemNameTable[itemID] or tostring(itemID)
        local color

        -- extract quality from string
        local s,e, quality = string.find( itemString, "/(%d+)" )
        if s then itemString = string.sub( itemString, 1, s-1 ) end
        if quality then color = MI2_QualityColor[tonumber(quality)] end

        return itemString, (color or mifontLightRed)
end -- MI2_GetLootItemString()


-----------------------------------------------------------------------------
-- MI2_AddItemsToTooltip()
--
-- Add one loot item description line to the tooltip. Item description
-- texts can optionally be shortened. Skinning loot uses skinned counter
-- instead of looted counter.
-----------------------------------------------------------------------------
local function MI2_AddOneItemToTooltip( mobData, itemID, amount, useFilter )
        local itemText, itemColor = MI2_GetLootItemString( itemID )

        -- apply item filter is requested
        if useFilter then
                if MobInfoConfig.ItemFilter ~= ""  then
                        local itemNotOK = string.find( string.lower(itemText), string.lower(MobInfoConfig.ItemFilter) ) == nil
                        if itemNotOK then return end
                end
        else
                itemColor = "* "..itemColor -- prefix for cloth and skinning loot
        end

        -- shorten item text to keep tooltip reasonably small
        local shortItemNames = true
        if shortItemNames and string.len(itemText) > 35 then
                itemText = string.sub(itemText,1,35).."..."
        end
        itemText = itemText..": "..amount

        local totalAmount = mobData.loots
        if miSkinLoot[itemID] then
                totalAmount = mobData.skinCount
        end
        if totalAmount and totalAmount > 0 then
                itemText = itemText.." ("..ceil(amount/totalAmount*100).."%)"  
        end
        
        GameTooltip:AddLine( itemColor..itemText )
end -- MI2_AddItemsToTooltip


-----------------------------------------------------------------------------
-- MI2_AddItemsToTooltip()
--
-- Add the list of items to the Mob tooltip. This function must be
-- called only for mobs that exist and that have an existing item list.
-- The item list gets printed in three parts: first all real non cloth and
-- non skinning loot items, then the skinning and then the cloth items.
-- The parts can be enabled/disabled separately.
--
-- Notoriously similar and numerous items that radically increase tooltip
-- size without being of much (if any) interest will be collapsed into
-- just one item (example: "Green Hills of Stranglethorn" pages).
-----------------------------------------------------------------------------
local function MI2_AddItemsToTooltip( mobData )
        local skinList = {}
        local clothList = {}
        local collapsedList = {}

        -- collapse almost identical items into one item
        for itemID, amount in mobData.itemList do
                if MI2_ItemCollapseList[itemID] then
                        local collapsedID = MI2_ItemCollapseList[itemID]
                        collapsedList[collapsedID] = (collapsedList[collapsedID] or 0) + amount
                end
        end

        -- first add all non cloth and non skin items to tooltip (apply item filter)
        for itemID, amount in mobData.itemList do
                local isSkin = miSkinLoot[itemID]
                local isCloth = miClothLoot[itemID]
                if isSkin then
                        skinList[itemID] = amount
                elseif isCloth then
                        clothList[itemID] = amount
                elseif MobInfoConfig.ShowItems == 1 and not MI2_ItemCollapseList[itemID] then
                        MI2_AddOneItemToTooltip( mobData, itemID, amount, true )
                end
        end

        -- add collapsed items
        for itemID, amount in collapsedList do
                MI2_AddOneItemToTooltip( mobData, itemID, amount, true )
        end

        if MobInfoConfig.ShowClothSkin == 1 then
                -- add all cloth and skinning items to tooltip
                for itemID, amount in skinList do
                        MI2_AddOneItemToTooltip( mobData, itemID, amount, false )
                end

                -- add all cloth and skinning items to tooltip
                for itemID, amount in clothList do
                        MI2_AddOneItemToTooltip( mobData, itemID, amount, false )
                end
        end
end -- MI2_AddItemsToTooltip


-----------------------------------------------------------------------------
-- MI2_AddLocationToTooltip()
--
-- Add the Mob location to the tooltip. Mob location always uses an entire
-- tooltip line.
-----------------------------------------------------------------------------
local function MI2_AddLocationToTooltip( location, showFullLocation )
        local x = floor( (location.x1 + location.x2) / 2 )
        local y = floor( (location.y1 + location.y2) / 2 )
        local zone = MI2_Zones[location.c][location.z]
        if zone then
                if showFullLocation then
                        GameTooltip:AddLine( mifontGold..MI_TXT_LOCATION..mifontWhite..zone.." ("..x.."/"..y..")" )
                else
                        GameTooltip:AddLine( mifontGold..MI_TXT_LOCATION..mifontWhite..zone )
                end
        end
end -- MI2_AddLocationToTooltip()


-----------------------------------------------------------------------------
-- MI2_CreateNormalTooltip()
--
-- add all collected mob data to the game tooltip, data is only added if
-- corresponding "Show" flag is set
-----------------------------------------------------------------------------
local function MI2_CreateNormalTooltip( mobData, mobIndex, showFullLocation )
        local copperAvg, itemValueAvg
        local addEmptyLine = 0
        
        if mobData.class and MobInfoConfig.ShowClass == 1 then
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_CLASS, mifontWhite..mobData.class )
        end

        if mobData.healthCur and MobInfoConfig.ShowHealth == 1 then
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_HEALTH, mifontWhite..mobData.healthCur.." / "..mobData.healthMax )
                MI2_HealthLine = GameTooltip:NumLines()
        end

        if mobData.manaMax and mobData.manaMax > 0 and MobInfoConfig.ShowMana == 1 then
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_MANA, mifontWhite..mobData.manaCur.." / "..mobData.manaMax )
                MI2_ManaLine = GameTooltip:NumLines()
        end

        -- exit right here if mob does not exist in database
        if not mobData.color then
                return
        end
        
        local mobGivesXp = not (mobData.color.r == 0.5  and  mobData.color.g == 0.5  and  mobData.color.b == 0.5)
        if mobGivesXp and mobData.xp then
                if MobInfoConfig.ShowXp == 1 then
                        GameTooltip:AddDoubleLine( mifontGold..MI_TXT_XP, mifontWhite..mobData.xp )
                end 
                if MobInfoConfig.ShowNo2lev == 1 then
                        GameTooltip:AddDoubleLine( mifontGold..MI_TXT_TO_LEVEL, mifontWhite..mobData.mob2Level )
                end 
        end

        if (mobData.minDamage or mobData.dps) and MobInfoConfig.ShowDamage == 1 then 
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_DAMAGE, mifontWhite..(mobData.minDamage or 0).."-"..(mobData.maxDamage or 0).."  ["..(mobData.dps or 0).."]" )
        end

        addEmptyLine = MobInfoConfig.ShowBlankLines

        if  MobInfoConfig.CombinedMode == 1  and  MobInfoConfig.ShowCombined == 1  then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddLine( mifontGray.."["..MI_TXT_COMBINED..mobData.combinedStr.."]" )
        end

        if mobData.kills and MobInfoConfig.ShowKills == 1 then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_KILLS, mifontWhite..mobData.kills )
        end          

        if  mobData.loots  and  MobInfoConfig.ShowLoots == 1  then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_TIMES_LOOTED, mifontWhite..mobData.loots )
        end

        if  mobData.emptyLoots  and  MobInfoConfig.ShowEmpty == 1  then
                local emptyLootsStr = mifontWhite..mobData.emptyLoots
                if  mobData.loots  then
                        emptyLootsStr = emptyLootsStr.." ("..ceil((mobData.emptyLoots/mobData.loots)*100).."%) "
                end
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_EMPTY_LOOTS, emptyLootsStr )
        end

        if mobData.clothCount and MobInfoConfig.ShowCloth == 1 then
                local clothStr = mifontWhite..mobData.clothCount
                if mobData.loots then
                        clothStr = clothStr.." ("..ceil((mobData.clothCount/mobData.loots)*100).."%) "
                end
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddDoubleLine( mifontGold..MI_TXT_CLOTH_DROP, clothStr )
        end

        if mobData.copper and mobData.loots then
                copperAvg = ceil( mobData.copper / mobData.loots )
                if MobInfoConfig.ShowCoin == 1 then
                        if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                        GameTooltip:AddDoubleLine(mifontGold..MI_TXT_COIN_DROP,mifontWhite..copper2text(copperAvg))
                end
        end

        if mobData.itemValue and mobData.loots then
                itemValueAvg = ceil( mobData.itemValue / mobData.loots )
                if MobInfoConfig.ShowIV == 1 then
                        if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                        GameTooltip:AddDoubleLine(mifontGold..MI_TEXT_ITEM_VALUE,mifontWhite..copper2text(itemValueAvg))
                end
        end

        local totalValue = (copperAvg or 0) + (itemValueAvg or 0)
        if totalValue > 0 and MobInfoConfig.ShowTotal == 1 then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddDoubleLine(mifontGold..MI_TXT_MOB_VALUE,mifontWhite..copper2text(totalValue))
        end

        if  mobData.qualityStr ~= ""  and  MobInfoConfig.ShowQuality == 1  then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                GameTooltip:AddDoubleLine(mifontGold..MI_TXT_QUALITY, mobData.qualityStr)
        end

        if mobData.location and MobInfoConfig.ShowLocation == 1 then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                MI2_AddLocationToTooltip( mobData.location, showFullLocation )
        end

        addEmptyLine = MobInfoConfig.ShowBlankLines

        if mobData.itemList and (MobInfoConfig.ShowItems == 1 or MobInfoConfig.ShowClothSkin == 1) then
                if addEmptyLine == 1 then GameTooltip:AddLine("\n") addEmptyLine = 0 end
                MI2_AddItemsToTooltip( mobData )
        end

  -----------------------------------------------------------------------
  -- debugging code : append actual database contents to end of tooltip
  -- enabled by setting local vairable "MI2_Debug"
  if MI2_Debug > 1 then
  GameTooltip:AddLine("----------------  D e b u g   I n f o  ----------------")
  GameTooltip:AddDoubleLine("[DBG] "..mifontGold.."index",mobIndex)
  if MobInfoDB[mobIndex] and MI2_PlayerName then
    if MobInfoDB[mobIndex].bi then GameTooltip:AddDoubleLine("[DBG] "..mifontGold.."[lt,el,cp,iv,cc,xp,mt]",mifontWhite..MobInfoDB[mobIndex].bi) end
    if MobInfoDB[mobIndex].qi then GameTooltip:AddDoubleLine("[DBG] "..mifontGold.."[r1,r2,r3,r4,r5]",mifontWhite..MobInfoDB[mobIndex].qi) end
    if MobInfoDB[mobIndex][MI2_PlayerName] then GameTooltip:AddDoubleLine("[DBG] "..mifontGold.."[kl,dmin,dmax,dps]",mifontWhite..MobInfoDB[mobIndex][MI2_PlayerName]) end
  end end
  -- end of debugging code
  -----------------------------------------------------------------------
end -- MI2_CreateNormalTooltip()


-----------------------------------------------------------------------------
-- MI2_CreateCompactTooltip()
--
-- add all collected mob data to the game tooltip, data is only added if
-- corresponding "Show" flag is set
-----------------------------------------------------------------------------
local function MI2_CreateCompactTooltip( mobData, mobIndex, showFullLocation )
        local firstLine = GameTooltip:NumLines() + 1

        if (mobData.healthCur or mobData.manaMax and mobData.manaMax > 0)  
                        and (MobInfoConfig.ShowHealth == 1 or MobInfoConfig.ShowMana == 1) then
                GameTooltip:AddDoubleLine( mifontGold.."HP    "..mifontWhite..(mobData.healthCur or 0).." / "..(mobData.healthMax or 0), mifontWhite..(mobData.manaCur or 0).." / "..(mobData.manaMax or 0)..mifontGold.." Mana" )
                MI2_HealthLine = GameTooltip:NumLines()
                if mobData.manaMax and mobData.manaMax > 0 then
                        MI2_ManaLine = MI2_HealthLine
                end
        end

        -- exit right here if mob does not exist in database
        if not mobData.color then
                return
        end

        local mobGivesXp = not (mobData.color.r == 0.5  and  mobData.color.g == 0.5  and  mobData.color.b == 0.5)
        if mobGivesXp and mobData.xp and (MobInfoConfig.ShowXp == 1 or MobInfoConfig.ShowNo2lev == 1) then
                GameTooltip:AddDoubleLine( mifontGold.."XP    "..mifontWhite..mobData.xp, mifontWhite..mobData.mob2Level..mifontGold.." KtL    " )
        end

        if (mobData.minDamage or mobData.dps) and MobInfoConfig.ShowDamage == 1 then
                GameTooltip:AddDoubleLine( mifontGold.."Dmg "..mifontWhite..(mobData.minDamage or 0).."-"..(mobData.maxDamage or 0), mifontWhite..(mobData.dps or 0)..mifontGold.." Dps   " )
        end

        if  MobInfoConfig.CombinedMode == 1  and  MobInfoConfig.ShowCombined == 1  then
                GameTooltip:AddLine( mifontGray.."["..MI_TXT_COMBINED..mobData.combinedStr.."]" )
        end

        if (mobData.kills or mobData.loots) and (MobInfoConfig.ShowKills == 1 or MobInfoConfig.ShowLoots == 1)  then
                GameTooltip:AddDoubleLine( mifontGold.."Kills  "..mifontWhite..(mobData.kills or 0), mifontWhite..(mobData.loots or 0)..mifontGold.." Loots" )
        end          

        if  (mobData.emptyLoots or mobData.clothCount) and (MobInfoConfig.ShowCloth == 1 or MobInfoConfig.ShowEmpty == 1)  then
                local emptyLootsStr = mifontWhite..(mobData.emptyLoots or 0)
                if  mobData.loots  then
                        emptyLootsStr = emptyLootsStr.." ("..ceil(((mobData.emptyLoots or 0)/mobData.loots)*100).."%) "
                end
                local clothStr = mifontWhite..(mobData.clothCount or 0)
                if mobData.loots then
                        clothStr = clothStr.." ("..ceil(((mobData.clothCount or 0)/mobData.loots)*100).."%) "
                end
                GameTooltip:AddDoubleLine( mifontGold.."CL     "..mifontWhite..clothStr, mifontWhite..emptyLootsStr..mifontGold.." EL      " )
        end

        if (mobData.copper or mobData.itemValue) and mobData.loots and MobInfoConfig.ShowTotal == 1 then
                local copperAvg = ceil( (mobData.copper or 0) / mobData.loots )
                local itemValueAvg = ceil( (mobData.itemValue or 0) / mobData.loots )
                local totalValue = copperAvg + itemValueAvg
                if totalValue > 0 then
                        GameTooltip:AddDoubleLine( mifontGold.."Val    "..mifontWhite..copper2text(totalValue), mifontWhite..copper2text(copperAvg)..mifontGold.." Coins" )
                end
        end

        if  mobData.qualityStr ~= ""  and  MobInfoConfig.ShowQuality == 1  then
                GameTooltip:AddLine( mifontGold.."Q      "..mifontWhite..mobData.qualityStr )
        end

        if mobData.location and MobInfoConfig.ShowLocation == 1 then
                MI2_AddLocationToTooltip( mobData.location, showFullLocation )
        end
        
        if mobData.itemList and (MobInfoConfig.ShowItems == 1 or MobInfoConfig.ShowClothSkin == 1) then
                MI2_AddItemsToTooltip( mobData )
        end
end -- MI2_CreateCompactTooltip()


-----------------------------------------------------------------------------
-- MI2_BuildQualityString()
--
-- Build a string drepresenting the loot quality overview for the given mob.
-----------------------------------------------------------------------------
local function MI2_BuildQualityString( mobData )
        local quality, chance
        local rt = mobData.loots or 1
 
        mobData.qualityStr = ""
        for idx = 1, 5 do
                quality = mobData["r"..idx]
                if quality then
                        chance = ceil( quality / rt * 100.0 )
                        if chance > 100 then chance = 100 end
                        mobData.qualityStr = mobData.qualityStr..MI2_QualityColor[idx]..quality.."("..chance.."%) "
                end
        end
end  -- MI2_CreateQualityString


-----------------------------------------------------------------------------
-- MI2_BuildMobInfoTooltip()
--
-- create game tooltip contents
-- this includes handling the combined mode where data for several mobs
-- with same name but different levels has to be added up
-----------------------------------------------------------------------------
function MI2_BuildMobInfoTooltip( mobName, mobLevel, showFullLocation )
        -- do not add anything to the tooltip for players
        if  UnitIsPlayer("mouseover")  then  return  end

        -- get mob data for hovered mob
        local mobData = MI2_GetMobData( mobName, mobLevel, "mouseover" )
        mobData.combinedStr = ""

        MI2_MouseoverIndex = mobName..":"..mobLevel

        -- handle combined Mob mode : try to find the other Mobs with same
        -- name but differing level, add their data to the tooltip data
        if  MobInfoConfig.CombinedMode == 1 and mobLevel > 0 then
                for levelToCombine = mobLevel-3, mobLevel+3, 1 do
                        if levelToCombine ~= mobLevel  then
                                local dataToCombine = MI2_GetMobData( mobName, levelToCombine )
                                if dataToCombine.color then
                                        MI2_AddTwoMobs( mobData, dataToCombine )
                                        mobData.combinedStr = mobData.combinedStr.." L"..levelToCombine
                                        mobData.color = GetDifficultyColor( levelToCombine )
                                end
                        else
                                mobData.combinedStr = mobData.combinedStr.." L"..levelToCombine
                        end
                end
        end

        -- if there is data about the "mouseover" mob in the target table it has to be added
        local dataToCombine = MI2_CurrentTargets[MI2_MouseoverIndex]
        if dataToCombine then
                MI2_AddTwoMobs( mobData, dataToCombine )
                if not mobData.color then mobData.color = {r=1.0;b=1.0;c=1.0} end
        end

        -- calculate number of mobs to next level based on mob experience
        if mobData.xp then
                local xpCurrent = UnitXP("player") + mobData.xp
                local xpToLevel = UnitXPMax("player") - xpCurrent
                mobData.mob2Level = ceil(abs(xpToLevel / mobData.xp))+1
        end

        -- display the Mob data to the game tooltip
        MI2_BuildQualityString( mobData )
        if MobInfoConfig.CompactMode == 1 then
                MI2_CreateCompactTooltip( mobData, MI2_MouseoverIndex, showFullLocation )
        else
                MI2_CreateNormalTooltip( mobData, MI2_MouseoverIndex, showFullLocation )
        end
end -- MI2_BuildMobInfoTooltip()


-----------------------------------------------------------------------------
-- MI2_DebugShowContainerItemInfo()
--
-- Show debugging info for the current hovered container item.
-- The info is added to the game tooltip.
-- This function is called only if "MI2_DebugItems > 0"
-----------------------------------------------------------------------------
local function MI2_DebugShowContainerItemInfo()
        local frame = GetMouseFocus()
        if frame then
                local frameName = (frame:GetName() or "")
                local _,_,parenFrameName, num = string.find( frameName, "(.+)Item(%d+)" )
                if parenFrameName and num then
                        local parentFrame = getglobal( parenFrameName )
                        if parentFrame then
                                local link = GetContainerItemLink( parentFrame:GetID(), frame:GetID() )
                                local _, _, itemID = string.find((link or ""), "|Hitem:(%d+):(%d+):(%d+):")
                                if IsControlKeyDown() then GameTooltip:AddLine( mifontSubWhite.."[frame="..frameName..",item="..link.."]" ) end
                                if itemID then
                                        local itemValue = MI2_BasePrices[tonumber(itemID)]
                                        if itemValue then
                                                GameTooltip:AddLine( mifontSubWhite.."[itemID="..itemID..",price="..itemValue.."]" )
                                        else
                                                GameTooltip:AddLine( mifontRed.."** NO PRICE ** "..mifontMageta.."item="..link..mifontMageta..",itemID="..itemID )
                                        end
                                        GameTooltip:Show()
                                end
                        end
                end
        end
end -- MI2_DebugShowContainerItemInfo()


-----------------------------------------------------------------------------
-- MI2_BuildItemDataTooltip()
--
-- Build the additional game tooltip content for a given item name.
-- If the item is a known loot item this function will add the names of
-- all Mobs that drop the item to the game tooltip. Each Mob name will
-- appear on its own line.
-----------------------------------------------------------------------------
function MI2_BuildItemDataTooltip( itemName )

        if MI2_DebugItems > 0 then MI2_DebugShowContainerItemInfo() end

        -- get the table of all Mobs that drop the item, exit if none
        local itemFound = MI2_XRefItemTable[itemName]
        if not itemFound then return false end

        -- Create a list of mobs dropping this item that is indexed by only
        -- the base Mob name. For each Mob calculate the chance to drop.
        -- Create a second list referencing the same data that is indexed
        -- numerically so that it can then be sorted by chance to get.
        local numMobs = 0
        local resultList = {}
        local sortList = {}
        for mobIndex, itemAmount in itemFound do
                local mobData = {}
                MI2_DecodeBasicMobData( nil, mobData, mobIndex )

                local mobName, mobLevel = MI2_GetIndexComponents( mobIndex )
                local itemData = resultList[mobName]
                if not itemData then
                        numMobs = numMobs + 1
                        itemData = { name = mobName, loots = 0, count = 0 }
                        resultList[mobName] = itemData
                        sortList[numMobs] = itemData
                end

                itemData.loots = itemData.loots + (mobData.loots or 0)
                itemData.count = itemData.count + itemAmount
                if itemData.loots > 0 then
                        itemData.chance = floor(100.0 * itemData.count / itemData.loots + 0.5)
                        if itemData.chance > 100 then itemData.chance = 100 end
                        if itemData.loots < 5 then
                                itemData.rating = itemData.chance + itemData.loots * 1000
                        else
                                itemData.rating = itemData.chance + 5000
                        end
                else
                        itemData.chance = itemData.count
                        itemData.rating = itemData.chance
                end
        end

        -- sort list of Mobs by chance to get
        table.sort( sortList, function(a,b) return (a.rating > b.rating) end  )

        -- add Mobs to tooltip
        GameTooltip:AddLine( mifontLightBlue..MI_TXT_DROPPED_BY..numMobs.." Mobs:" )
        if numMobs > 8 then numMobs = 8 end
        for idx = 1, numMobs do
                local data = sortList[idx]
                if data.loots > 0 then
                        GameTooltip:AddDoubleLine( mifontLightBlue.."  "..data.name, mifontWhite..data.chance.."% ("..data.count.."/"..data.loots..")" )
                else
                        GameTooltip:AddDoubleLine( mifontLightBlue.."  "..data.name, mifontWhite..data.chance )
                end
        end
        if sortList[9] then
                GameTooltip:AddLine( mifontLightBlue.."  [...]" )
        end

        return true
end -- MI2_BuildItemDataTooltip()


-----------------------------------------------------------------------------
-- MI2_DpsCalculation()
--
-- Calculate an updated DPS (damage per second) based on the current target
-- data in "MI2_Target" and the new damage value given as parameter.
-----------------------------------------------------------------------------
function MI2_DpsCalculation( damage, contextInfo )

if not damage then
        chattext( "MI2_ERROR: chat message parse error in "..contextInfo )
        return
end

        -- calculate and update DPS for target
        if not MI2_Target.FightStartTime then
                MI2_Target.FightStartTime = GetTime() - 1.0
                MI2_Target.FightEndTime = GetTime()
                MI2_Target.FightDamage = damage
        elseif MI2_Target.FightEndTime then
                MI2_Target.FightEndTime = GetTime()
                MI2_Target.FightDamage = MI2_Target.FightDamage + damage
        end
end  -- MI2_DpsCalculation()


-----------------------------------------------------------------------------
-- MI2_EventSelfMelee()
--
-- handler for event CHAT_MSG_COMBAT_SELF_HITS
-- handles normal and critical melee damage
-----------------------------------------------------------------------------
function MI2_EventSelfMelee( )
        local dmgText = arg1

        -- normal melee damage
        for  creature, damage in string.gfind( dmgText, MI_PARSE_SELF_MELEE ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_MELEE" )
                return
        end

        -- critical melee damage
        for  creature, damage in string.gfind( dmgText, MI_PARSE_SELF_MELEE_CRIT ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_MELEE_CRIT" )
                return
        end
end  -- MI2_EventSelfMelee()


-----------------------------------------------------------------------------
-- MI2_EventSelfSpell()
--
-- handler for event "CHAT_MSG_SPELL_SELF_DAMAGE"
-- handles normal and critical spell damage and damage done by bows/guns
-----------------------------------------------------------------------------
function MI2_EventSelfSpell( )
        local dmgText = arg1

        -- normal spell damage
        for  spell, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_SPELL ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_SPELL" )
                return
        end

        -- critical spell damage
        for  spell, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_SPELL_CRIT ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_SPELL_CRIT" )
                return
        end

        -- damage done by bows/guns
        for  spell, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_BOW ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_BOW" )
                return
        end

        -- critical damage done by bows/guns (for German this will get parsed above as "normal spell")
        for  spell, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_BOW_CRIT ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_BOW" )
                return
        end
end -- MI2_EventSelfSpell()


-----------------------------------------------------------------------------
-- MI2_EventSelfPet()
--
-- handler for event "CHAT_MSG_COMBAT_PET_HITS" and "CHAT_MSG_SPELL_PET_DAMAGE"
-- handles normal and critical melee/spell damage done by players pet
-----------------------------------------------------------------------------
function MI2_EventSelfPet( )
        local dmgText = arg1

        -- damage done by players pet
        for  petName, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_PET ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_PET" )
                return
        end

        -- critical damage done by players pet
        for  petName, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_PET_CRIT ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_PET_CRIT" )
                return
        end

        -- damage done by spells of players pet
        for  petName, spell, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_PET_SPELL ) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_PET_SPELL" )
                return
        end

        -- critical damage done by spells of players pet
        for  petName, spell, creature, damage in string.gfind( dmgText, MI_PARSE_SELF_PET_SPELL_CRIT) do
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_PET_SPELL_CRIT" )
                return
        end
end -- MI2_EventSelfPet()


-----------------------------------------------------------------------------
-- MI2_EventSpellPeriodic()
--
-- handler for event "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"
-- handles periodic damage done by spells
-----------------------------------------------------------------------------
function MI2_EventSpellPeriodic( )
        local dmgText = arg1

        -- periodic spell damage
        for  dummy, damage, damageType, spell in string.gfind( dmgText, MI_PARSE_SELF_SPELL_PERIODIC ) do
                if GetLocale()=="zhTW" then
                        spell, dummy, damage, damageType = dummy, damage, damageType, spell;
                end
                MI2_DpsCalculation( tonumber(damage), "PARSE_SELF_SPELL_PERIODIC" )
                return
        end
end -- MI2_EventSpellPeriodic()


-----------------------------------------------------------------------------
-- MI2_EventCreatureDiesXP()
--
-- event handler for the chat message telling us that a creature died
-- and gave us XP points
-----------------------------------------------------------------------------
function MI2x_EventCreatureDiesXP()
        local idx = MI2_LastTargetIdx

        -- capture kills giving XP
        -- sometimes the kill deselects current target, sometimes it doesn't
        -- yet to properly record a kill the idx (ie. name and level) is required
        for creatureName, xp in string.gfind(arg1, MI_MOB_DIES_WITH_XP ) do
                if not idx or creatureName == MI2_Target.name then
                        idx = MI2_Target.index
                end
                if idx then
                        MI2_RecordKill( idx, tonumber(xp) )
                        MI2_RecordLocation( idx )
                end
        end
end -- MI2_EventCreatureDiesXP()


-----------------------------------------------------------------------------
-- MI2_CreatureDiesHostile()
--
-- Event handler for chat message telling me that a hostile creature in
-- my vicinity has died. The kill will only get counted if my current
-- targets name is identical to the name of the creature that died, and if
-- I have actually been in a fight with the mob.
-----------------------------------------------------------------------------
function MI2x_CreatureDiesHostile()
        local index = MI2_Target.mobIndex
        if index and UnitIsDead("target") then
                -- kills without XP never deselect the current target
                for creatureName in string.gfind(arg1, MI_MOB_DIES_WITHOUT_XP ) do
                        if creatureName == MI2_Target.name and MI2_Target.FightStartTime then
                                MI2_RecordKill( index, 0 )
                                MI2_RecordLocation( index )
                        end
                end
        end
end -- MI2_CreatureDiesHostile()


-----------------------------------------------------------------------------
-- MI2_UpdateMobInfoState()
--
-- Enable or disable all Mob ToolTip options depending on state of
-- "DisableMobInfo". Update event handlers accordingly.
-----------------------------------------------------------------------------
function MI2_UpdateMobInfoState()
        local children = { MI2_FrmTooltipOptions:GetChildren() }

        -- update MobInfo options dialog
        for index, frame in children do
                if frame ~= MI2_OptDisableMobInfo and frame ~= MI2_OptItemFilter then
                        if MobInfoConfig.DisableMobInfo == 0 then
                                frame:Enable()
                                getglobal(frame:GetName().."Text"):SetTextColor( 1.0, 0.8, 0.0 )
                        else
                                frame:Disable()
                                getglobal(frame:GetName().."Text"):SetTextColor( 0.5, 0.5, 0.5 )
                        end
                end
        end

        MI2_InitializeEventTable()
end  -- MI2_UpdateMobInfoState()

-----------------------------------------------------------------------------
-- MI2_UpdateTooltipHealthMana()
--
-- Update the health and mana values in the Mob tooltip, if they exist.
-----------------------------------------------------------------------------
function MI2_UpdateTooltipHealthMana( healthCur, healthMax )
        local tooltip = "GameTooltip"
        if TipBuddyTooltip then tooltip = "TipBuddyTooltip" end
        if MI2_HealthLine and healthCur then
                local healthText = healthCur.." / "..healthMax
                if MobInfoConfig.CompactMode == 1 then
                        local healthLine = getglobal(tooltip.."TextLeft"..MI2_HealthLine)
                        healthLine:SetText( mifontGold.."HP    "..mifontWhite..healthText )
                else
                        local healthLine = getglobal(tooltip.."TextRight"..MI2_HealthLine)
                        healthLine:SetText( mifontWhite..healthText )
                end
        end

        if MI2_ManaLine then
                local manaText = mifontWhite..UnitMana("mouseover").." / "..UnitManaMax("mouseover")
                local manaLine = getglobal(tooltip.."TextRight"..MI2_ManaLine)
                if MobInfoConfig.CompactMode == 1 then
                        manaLine:SetText( manaText..mifontGold.." Mana" )
                else
                        manaLine:SetText( manaText )
                end
        end
end -- MI2_UpdateTooltipHealthMana()