SpeedO - Display

SpeedO displays your your current travel speed and map coordinates in a minimalistic way.

Displayed Info:

  • X, Y location
  • Speed in % of running. [Note 1]
  • Speed % horizontally over the map. (excludes loss of momentum when ascending or descending)
  • Degrees
  • Pitch angle


  • Small format to not be a distraction or waste screen space.
  • Can be moved to any place on the screen but by default it is displayed in the very top left corner of your screen.

Note 1) Blizzard considers running to be 100%. Speeds are shown based on that. Specific speeds in units such as yards, meters, etc can not be done because the map units are relative to the zone map size. 1 unit in one zone is not the same size as 1 unit in another.

Thanks to "WhatNamesAreLeft" for contributing to this version.

Bug Reports?

Suggestions, comments?

Elektrode (Grizzly Hills)

You must login to post a comment. Don't have an account? Register to get one!

  • Avatar of xhuli xhuli Apr 10, 2014 at 14:00 UTC - 0 likes

    You can unite the rounding and padding:

    PitchDeg = format("%.0f", PitchDeg)
    PitchDeg = LPad(PitchDeg, 3, " ")
    MapX = format("%.1f", MapX)
    MapX = LPad(MapX, 6, " ")


    PitchDeg =  string.format("%3.0f", PitchDeg)
    MapX = string.format("%6.1f", MapX)

    Built-in functions are claimed to be faster.

  • Avatar of xhuli xhuli Apr 10, 2014 at 13:45 UTC - 0 likes

    Getting player X, Y coordinates can be buggy with this method:

    PositionX, PositionY = GetPlayerMapPosition("player")

    Player can open the map and navigate away from the zone he is in.

    To overcome that, this add-on SilverDragon uses comparison:

    -- zone displayed on the map, might not be where the player is
    local MapZoneID = GetCurrentMapAreaID();
    -- bring the map to the zone where the player is
    local PlayerZoneID = GetCurrentMapAreaID();
    local PositionX, PositionY = GetPlayerMapPosition("player");
    if PlayerZoneID ~= MapZoneID and WorldMapFrame:IsVisible() then
    	-- politely display back the map the player was looking at
  • Avatar of xhuli xhuli Apr 07, 2014 at 09:42 UTC - 0 likes

    @FireSerpent about your suggestion for color-heading, here is a small Lua function that implements: http:en.wikipedia.org/wiki/File:Compass_Card.png

    note: I tried it on a heading text without a background and sometimes it`s hard to read the value.

    local function hsl2rgb( hue, alpha, saturation, lightness )
    	-- multiply the end result with 255 to get [0-255] RGB values
    	-- R = math.floor( r * 255 );
    	-- G = math.floor( g * 255 );
    	-- B = math.floor( b * 255 );
    	-- http://stackoverflow.com/questions/10393134/converting-hsl-to-rgb
    	-- http://en.wikipedia.org/wiki/HSL_and_HSV
    	-- http://basecase.org/env/on-rainbows
    	-- 0 < hue < 360 in degrees
    	-- 0 < hue < 2Pi in radians
    	-- 0 < saturation < 1; usually 1
    	-- 0 < lightness < 1; usually 0.5
    	if not hue then hue = 0 end;
    	if not alpha then alpha = 0.7 end;
    	if not saturation then saturation = 1 end;
    	if not lightness then lightness = 0.5 end;
    	if saturation < 0 or saturation > 1 then return 0, 0, 0, alpha end;
    	if lightness < 0 or lightness > 1 then return 0, 0, 0, alpha end;
    	local chroma = ( 1 - math.abs(2*lightness - 1) ) * saturation;
    	local m = lightness - chroma/2;
    	local h = hue%(2*math.pi);
    	-- h' = h[degrees]/60 = 3*h[radians]/pi = 0.954929.. * h[radians]
    	local h = 0.95492965855137201461330258023509 * h;
    	local x =( 1 - math.abs(h%2 - 1) ) * chroma;
    	local r, g, b = 0, 0, 0;
    	if     h < 1 then r, g, b = chroma, x, 0;
    	elseif h < 2 then r, g, b = x, chroma, 0;
    	elseif h < 3 then r, g, b = 0, chroma, x;
    	elseif h < 4 then r, g, b = 0, x, chroma;
    	elseif h < 5 then r, g, b = x, 0, chroma;
    	else r, g, b = chroma, 0, x;
    	return r+m, g+m, b+m, alpha
    -- this is not complete, just to get the picture
    local HeadRad = 2*math.pi - GetPlayerFacing("Player");
    MainFrame.txtHeadingDeg = MainFrame.txtHeadingDeg or MainFrame:CreateFontString("txtHeadingDeg");
    MainFrame.txtHeadingDeg:SetText( string.format("%d°", math.deg(HeadRad)) );
    MainFrame.txtHeadingDeg:SetVertexColor( hsl2rgb(HeadRad, 1) );
    Last edited Apr 10, 2014 by xhuli: code sample corrections
  • Avatar of FireSerpent FireSerpent Apr 03, 2014 at 15:22 UTC - 0 likes

    This shows the map position and continent coords of the pointer location on the tooltip, can also set waypoint with shift click. Text needs to be rounded though. Perhaps the tooltip could be placed farther away from the pointer as well. Perhaps it should instead be a function, called by WorldMapButton:HookScript("OnEnter",functionname)

    updated 4-3-2014 @ 10am (breaks tooltip when hovering pointer over monsters while map is open, need to fix)

    -- globals: cursorX, cursorY
    -- placed inside KSpeedO_OnUpdate
    if WorldMapFrame:IsShown() then -- IsVisible() also?
    	local x,y = GetCursorPosition()
    	if MouseIsOver(WorldMapFrame) and (x >= 15) and (x <= 385) and (y >=390) and (y <=635) then
    		x = (x - 15) / 370 ; y = (635 - y) / 245
    		cursorX = (1.0 - x) * ZoneWest + x * ZoneEast
    		cursorY = (1.0 - y) * ZoneNorth + y * ZoneSouth
    		x = x * 100 ; y = y * 100
    		GameTooltip:SetOwner(MainFrame, "ANCHOR_CURSOR")
    		GameTooltip:SetText(cursorX.." "..cursorY.."\r"..x.." "..y);
    		-- WorldMapFrame:HookScript("OnLeave",function (self,_) GameTooltip:Hide() end)
    		if (not MouseIsOver(MainFrame)) then GameTooltip:Hide() end
    -- placed by itself
    function SetWaypoint(self,button)
    	if IsShiftKeyDown() then
    		pointB_x = cursorX
    		pointB_y = cursorY
    Last edited Apr 03, 2014 by FireSerpent
  • Avatar of FireSerpent FireSerpent Mar 31, 2014 at 01:28 UTC - 1 like

    Using Pythagoras theorem I've included accurate vertical airspeed.

    -- these 4 lines aren't code, just the math it uses
    hs - horz speed, vs - vert speed, rs resultant speed
    hs^2 + vs^2 = rs^2
    vs^2 = rs^2 - hs^2
    vs = sqrt (rs^2 - hs^2)

    code here (last updated 3/31/2014 @ 2:40pm

    -- globals: odometer_horz, odometer_z, odometer_res
    -- globals: PrevContX,PrevContY
    -- globals: pointB_x, pointB_y
    -- calculate distances
    local dist_res = Speed * Elapsed
    local dist_x = ContinentX - PrevContX
    local dist_y = ContinentY - PrevContY
    local dist_horz = math.sqrt(math.pow(dist_x,2) + math.pow(dist_y,2))
    -- calcualte speeds
    local speed_horz = dist_horz / Elapsed
    if speed_horz > Speed then speed_horz = Speed end
    local speed_z = math.sqrt(math.pow(Speed,2) - math.pow(speed_horz,2)) -- can't know if it's up or down
    -- x and y speeds (note that Speed is speed_res)
    -- local speed_x = dist_x / Elapsed
    -- local speed_y = dist_y / Elapsed
    -- deduce z distance (can't know if it's up or down)
    local dist_z = speed_z * Elapsed
    -- move_yaw isn't playerfacing when strafing
    local move_yaw_rad = math.atan2(-1*dist_x, dist_y)	-- reverse X sign, math assumes right is pos
    local move_yaw_deg = move_yaw_rad * 180 / math.pi
    if move_yaw_deg < 0 then move_yaw_deg = (360 + move_yaw_deg) end	--convert to 0 to 360 format
    -- can't know if move_pitch is up or down (because you can pitch down and move up, for example)
    local move_pitch_rad = math.asin(dist_z / dist_res)
    local move_pitch_deg = move_pitch_rad * 180 / math.pi
    -- odometer
    odometer_horz = odometer_horz + dist_horz
    odometer_z = odometer_z + dist_z
    odometer_res = odometer_res + dist_res
    -- waypoint distance and ETA
    local dist_pointB_x = pointB_x - ContinentX
    local dist_pointB_y = pointB_y - ContinentY
    local dist_pointB_horz = math.sqrt(math.pow(dist_pointB_x,2) + math.pow(dist_pointB_y,2))
    local ETA = dist_pointB_horz / speed_horz	-- in seconds
    -- waypoint direction
    local dir_to_pointB_rad = math.atan2(-1*dist_pointB_x, dist_pointB_y)	-- reverse X sign, math assumes right is pos
    local dir_to_pointB_deg = dir_to_pointB_rad * 180 / math.pi
    if dir_to_pointB_deg < 0 then dir_to_pointB_deg = (360 + dir_to_pointB_deg) end	--convert to 0 to 360 format
    -- save for next frame
    PrevContX = ContinentX
    PrevContY = ContinentY
    -- round  for compact display (make sure this is done after calculations)
    -- you don't want to round the globals, that rounding error adds up!!!
    speed_horz = Round(speed_horz,0)
    speed_z = Round(speed_z,0)
    Last edited Mar 31, 2014 by FireSerpent
  • Avatar of FireSerpent FireSerpent Mar 30, 2014 at 23:06 UTC - 0 likes

    Speedometer that verifies continent coordinates are of the correct scale. Also it measures your speed while standing on a moving ship. To make this work I had to disable update interval so the addon runs every frame (otherwise you'd have to at least add up the Elapsed every frame, and it would only be accurate for straight lines.) Moving on a map diagonally you can tell if the scale is correct if the speed based on distance and elapsed is the same as the speed from GetUnitSpeed().

    -- check my next post for the improved code

    I've tested it out and so far the continent coordinates are of the proper scale.

    I've verified that strafing while pitched up/down will mess up the current method of calculating horizontal speed, based on this case:

    • Pitch 45deg (up or down)
    • User pushes forward and sideways
    • 29 yards / second Resultant_speed
    • Horizontal speed using Resultant_Speed * math.cos(PitchRad) = 20 yards / sec
    • Horizontal speed calculated using continent coords and elapsed = 25 yard / sec (accurate in this case)

    note: it's even worse if pitch is 89deg and the player is only strafing: (1 yard / sec) vs the accurate (29 yards / sec)

    The cosine method is only accurate when

    • user is pitch 0, strafe doesn't matter
    • user is pitched but not strafing

    You can also determine the horizontal velocity vector, which isn't playerfacing if they are strafing.

    Last edited Mar 31, 2014 by FireSerpent
  • Avatar of FireSerpent FireSerpent Mar 30, 2014 at 20:53 UTC - 0 likes

    Caves are a bit confusing. When you enter the house in Moonbrook,Westfall you eventually endup in a cave before you enter the actual Deadmines instance where the map title still says Westfall (although in the map rectangle it says deadmines) and you still interact with others not in your party. However while in this cave if you press M then right click your coordinates change. I don't know yet how the addon is supposed to know if you're in one of these mini-dungeons, nor where to find the boundaries for them.

    update: with this code the addon can know if it's a cave or not, however it requires the user press m for it to update

    local texture,w,h,isCave,caveName = GetMapInfo()
    if isCave then Msg = Msg.."isCave " end
    if not isCave then Msg = Msg.."not Cave "end

    I think these caves are called micro dungeons http:wowpedia.org/Micro_dungeon_maps

    UPDATE: GetCurrentMapZone does give the proper boundaries for micro dungeons, however I don't know which dbc the info is from. I don't where it gets the minimap tiles from.

    Last edited Apr 03, 2014 by FireSerpent
  • Avatar of FireSerpent FireSerpent Mar 30, 2014 at 05:16 UTC - 0 likes

    I thought I'd write here for reference what I've found out about DungeonMap.dbc What I've learned of the columns of the rows so far:

    unknown, instanceMapID, level, east, south, west, north, unknown

    instanceMapID is obtained from the 8th return variable of http:wowpedia.org/API_GetInstanceInfo

    the border order is different compared to WorldMapArea.dbc

    • eswn - east, south, west, north for DungeonMap.dbc
    • wens - west east north south for WorldMapArea.dbc

    While GetCurrentMapZone will give you the boundary values of WorldMapArea.dbc, I'm not sure if there's an equivalent of that for DungeonMap.dbc. Whenever you get zeros from GetCurrentMapZone I think it's then safe to assume that it is a dungeon. For example the mapID 690 for the stockades is all zeros in WorldMapArea.dbc, but the stockades is also instanceMapID 34 from which you can get the boundary info from DungeonMap.dbc.

    In WorldMapArea.dbc the instanceMapID takes the place of the continent column.Temple of Ahn'Qiraj is MapID 766, instanceMapID of 531. In DungeonMap.dbc you'll see levels 1,2,3 for instanceMapID 531 which matches the number of levels in that dungeon (odd that it's not zeros in WorldMapArea.dbc however.)

    Last edited Mar 30, 2014 by FireSerpent
  • Avatar of FireSerpent FireSerpent Mar 29, 2014 at 04:58 UTC - 0 likes

    That's great news! Once you have continent coordinates a horizontal odometer could easily be created by adding up the distance_traveled frame to frame. Frametime calculated from the time elapsed since the previous frame. The true horizontal speed calculated (true even when the player is strafing, or standing on a moving boat) using distance_traveled and frametime. I mistakenly wrote meters, I'm pretty sure continent coordinates are in yards.

    Perhaps after that is implemented a routing system could then be added where players would create a pointB waypoint (that is also converted to continent coordinates) by clicking on the map then it would give the heading the player should head & the estimated time of arrival based on current horizontal speed. Then you could tell your party members exactly how long it will take to fly to a specific location. If Blizzard were ever to give back the GetCurrentPosition function for Z coordinates even pitch could calculated for very high places like Mount Hyjal.

    I made a YouTube playlist demonstrating coordinate conversion on a scrollable-zoomable map (actually it converts in reverse: from web-page coords to continent coords to map coordinates.) The Azeroth video should finish uploading by the end of the day at Your text to link here... I haven't yet put the web-app online. The Azeroth map is actually 1000 png images converted directly from WoW minimap blp images. Modern computers/browsers can actually show that many at one time and still not get bogged down too much.

    The game gives heading in degrees CCW from north, but aviation is degrees CW from north. I'm thinking it's best to display aviation format so that pilots feel comfortable using it.

    Last edited Mar 29, 2014 by FireSerpent: heading format, CW or CCW
  • Avatar of KippG KippG Mar 29, 2014 at 02:54 UTC - 0 likes

    @FireSerpent: Go

    It looks like I'll have continent coords for you sometime soon.


Date created
Dec 26, 2011
Last update
Apr 03, 2014
Development stage
  • enUS
All Rights Reserved
Curse link
Recent files
  • A: r16 for 5.4.7 Apr 03, 2014
  • A: r15 for 5.4.7 Mar 29, 2014
  • R: v1.1 for 5.4.1 Dec 08, 2013
  • R: v1.04 for 5.1.0 Dec 10, 2012
  • R: v1.03 for 5.1.0 Dec 03, 2012