Revision 37 (by barfolomeu, 2011/04/27 00:40:00) Bump TOC for 4.1.
Nuke Astrolabe in favor of LibMapData.
Update the config to use the new function for setting the arrow location.
Simplify distance calculation.
Fix tracking of focus.
Fix some formatting.
--[[

Copyright 2008-∞ Daniel Ceregatti (daniel@ceregatti.org)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.

Special thanks to:

Shefki
Adys
kagaro

And everyone else in #wowace on irc.freenode.org

]]--

-- Locale variable
local L = LibStub("AceLocale-3.0"):GetLocale("DirectionArrow", false)

-- LibMapData-1.0 for zone sizes
local mapfiles = LibStub("LibMapData-1.0")
local width = 100
local height = 100

local current_unit = nil
local current_unit_guid = nil
local DistanceMetric = L["yards"]

local s2 = sqrt(2)
local cos, sin, rad, atan2, sqrt = math.cos, math.sin, math.rad, math.atan2, math.sqrt
local strfind = string.find
local GetPlayerFacing, GetPlayerMapPosition, GetMouseFocus, SecureButton_GetModifiedUnit, UnitIsUnit =
  GetPlayerFacing, GetPlayerMapPosition, GetMouseFocus, SecureButton_GetModifiedUnit, UnitIsUnit

--@debug@
local function pp (arg)
  if DirectionArrow.db.profile.debug then
    print (arg)
  end
end
--@end-debug@

local function CalculateCorner(r)
  return 0.5 + cos(r) / s2, 0.5 + sin(r) / s2;
end

local function RotateTexture(texture, angle)
  local LRx, LRy = CalculateCorner(angle + 0.785398163);
  local LLx, LLy = CalculateCorner(angle + 2.35619449);
  local ULx, ULy = CalculateCorner(angle + 3.92699082);
  local URx, URy = CalculateCorner(angle - 0.785398163);
  texture:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy);
end

local function GetBearing (unit)
  tx, ty = GetPlayerMapPosition(unit)
  if tx == 0 and ty == 0 then
    --@debug@
    pp (format ("Unable to obtain the position for unit %s", unit))
    --@end-debug@
    return 999
  end
  px, py = GetPlayerMapPosition("player")
  return -GetPlayerFacing() - atan2 (tx - px, py - ty)
end

function DistanceCalculator ()
  return sqrt ((((tx - px) * width) ^ 2) + (((ty - py) * height) ^ 2))
end

local function CanShow (unit)
  local bearing = GetBearing (unit)
  if bearing == 999 then
    return false
  end
  return true
end

local function OnUpdate (self, elapsed)
  self.elapsed = self.elapsed + elapsed
  if (self.elapsed > DirectionArrow.db.profile.update_frequency) then
    self.elapsed = 0
    local bearing = GetBearing (current_unit)
    if bearing == 999 then
      DirectionArrow.f:Hide()
      return
    end
    RotateTexture (DirectionArrow.t, bearing)
    DirectionArrow.x:SetText(format ("%d ", DistanceCalculator()))
  end
end

local function GetRaidIndex (GUID)
  for i = 1, GetNumRaidMembers() do
    if GUID == UnitGUID("raid"..i) then
      return i
    end
  end
  return 0
end

local function GetPartyIndex (GUID)
  for i = 1, GetNumPartyMembers() do
    if GUID == UnitGUID("party"..i) then
      return i
    end
  end
  return 0
end

local function GetUnitIndex (GUID)
  local RaidIndex = GetRaidIndex(GUID)
  local TargetFrom = ''
  if RaidIndex > 0 then
    TargetFrom = 'raid'
  else
    RaidIndex = GetPartyIndex (GUID)
    if RaidIndex > 0 then
      TargetFrom = 'party'
    end
  end
  --@debug@
  pp (format ("GetUnitIndex() returns: %d, %s", RaidIndex, TargetFrom))
  --@end-debug@
  return RaidIndex, TargetFrom
end

-- Keybindings
BINDING_HEADER_DIRECTIONARROW = "DirectionArrow"
BINDING_NAME_DIRECTIONARROW_TOGGLE = L["Sticky Tracking"]

DirectionArrow = LibStub("AceAddon-3.0"):NewAddon("DirectionArrow", "AceConsole-3.0", "AceEvent-3.0")

function DirectionArrow:OnInitialize()
  self.db = LibStub("AceDB-3.0"):New("DirectionArrowDB", DirectionArrowDefaultConfigDB, "Default")
  self.sticky = false
  self.f = CreateFrame("Button", nil, UIParent)
  self.f:SetScale(self.db.profile.size)
  self.f:SetPoint("CENTER")
  self.f:SetFrameLevel(20)
  self.f:EnableMouse(false)
  self.f:SetAlpha(self.db.profile.opacity)
  self.f:SetWidth(32)
  self.f:SetHeight(32)
  self.f:Hide()
  self.f.elapsed = 0
  self.f:SetScript("OnUpdate", OnUpdate)

  self.t = self.f:CreateTexture("OVERLAY")
  self.f.texture = t
  self.t:SetTexture([[Interface\Minimap\MinimapArrow]])
  self.t:SetBlendMode("BLEND")
  self.t:SetAlpha(1)
  self.t:SetAllPoints(self.f)

  self.x = self.f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  if self.db.profile.show_distance then
    self.x:Show()
  else
    self.x:Hide()
  end
  self.x:SetWidth(32)
  self.x:SetHeight(32)
  self.x:SetPoint("CENTER", self.f, "CENTER", self.db.profile.text_location_x, self.db.profile.text_location_y)
  self.x:SetTextColor(unpack (self.db.profile.distance_color))
  self.x:SetShadowOffset(.5,-.5)
  self.x:SetShadowColor(unpack (self.db.profile.distance_ds_color))
  self.x:SetAlpha(1)
  self.x:SetFont(self.x:GetFont(), self.db.profile.distance_size)

  LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("DirectionArrow", DirectionArrowConfig)

  self.optionsFrames = {}
  local ACD3 = LibStub("AceConfigDialog-3.0")
  self.optionsFrames.DirectionArrow = LibStub("LibAboutPanel").new (nil, "DirectionArrow")

  -- Thanks, Omen!
  self.optionsFrames.General = ACD3:AddToBlizOptions("DirectionArrow", L["General"], "DirectionArrow", "General")
  self:RegisterChatCommand("directionarrow", function()
    InterfaceOptionsFrame_OpenToCategory(self.optionsFrames.General)
    InterfaceOptionsFrame_OpenToCategory(self.optionsFrames.DirectionArrow)
  end)
  self:RegisterChatCommand("da", function()
    InterfaceOptionsFrame_OpenToCategory(self.optionsFrames.General)
    InterfaceOptionsFrame_OpenToCategory(self.optionsFrames.DirectionArrow)
  end)
  self.f:SetScript("OnEvent", function(frame,event,...) self[event](self,...) end)
  self.f:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
  self.f:RegisterEvent("RAID_ROSTER_UPDATE")
  mapfiles:RegisterCallback("MapChanged", function (event, map, floor, w, h)
    --@debug@
    pp (format ("Map changed. Map: %s, floor: %s, w: %s, h: %s", map, floor, w, h))
    --@end-debug@
    width = w
    height = h
  end)
end

function DirectionArrow:RAID_ROSTER_UPDATE()
  --@debug@
  pp ("RAID_ROSTER_UPDATE fired")
  --@end-debug@
  -- Get the unit for the saved GUID
  current_unit, _ = GetUnitIndex (current_unit_guid)
end

function DirectionArrow:ToggleSticky()
  DirectionArrow.sticky = not DirectionArrow.sticky
  --@debug@
  pp ("Sticky toggled. Set to: " .. (DirectionArrow.sticky and "true" or "false"))
  --@end-debug@
end

function DirectionArrow:UPDATE_MOUSEOVER_UNIT()
  if DirectionArrow.sticky then
    return
  end
  local frame = GetMouseFocus()
  if frame then
    local unit = SecureButton_GetModifiedUnit (frame)
    if
      unit
      and (
        strfind (unit, "^raid%d+$")
        or strfind (unit, "^raidpet%d+$")
        or strfind (unit, "^party%d+$")
        or strfind (unit, "^partypet%d+$")
        or strfind (unit, "^target.*$")
        or strfind (unit, "^focus.*$")
      )
    then
      if UnitIsUnit (unit, "player") then
        --@debug@
        pp ("The unit is the player")
        --@end-debug@
        return
      end
      --@debug@
      pp ("Setting unit: " .. unit)
      --@end-debug@
      current_unit = unit
      current_unit_guid = UnitGUID (unit)
      DirectionArrow.current_frame = frame
      if CanShow (current_unit) then
        DirectionArrow:SetArrow ()
        DirectionArrow.f:SetFrameLevel(frame:GetFrameLevel() + 50)
        DirectionArrow.f:Show()
        return
      end
    --@debug@
    elseif unit then
      pp ("The unit didn't match: " .. unit)
    --@end-debug@
    end
  end
end

function DirectionArrow:SetArrow ()
    DirectionArrow.f:ClearAllPoints ()
    DirectionArrow.f:SetPoint (
        "CENTER",
        DirectionArrow.current_frame,
        DirectionArrow.db.profile.relative_point,
        DirectionArrow.db.profile.relative_location_x,
        DirectionArrow.db.profile.relative_location_y
    )
end