--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- OBJECTIVE FUNCTIONS
-- Provides some wrappers to let us fire-and-forget details about objectives
-- and bypass the internal workings.
--
-- (c) 2005 Relic Entertainment
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

--? @group Objectives

COUNTER_TimerDecreasing	= 0
COUNTER_TimerIncreasing	= 1
COUNTER_Count			= 2
COUNTER_CountUpTo		= 3

-- ICON TYPES
IT_P_Attack 		= "objectives/objective_primary_attack"
IT_P_Capture 		= "objectives/objective_primary_capture"
IT_P_Defend 		= "objectives/objective_primary_defend"
IT_P_Garrison 		= "objectives/objective_primary_garrison"
IT_P_Move 			= "objectives/objective_primary_move"
IT_P_Repair 		= "objectives/objective_primary_repair"
IT_P_Upgrade 		= "objectives/objective_primary_upgrade"
IT_P_Default 		= "objectives/objective_primary"
IT_S_Default 		= "objectives/objective_secondary"
IT_M_Default 		= "objectives/objective_medal"
IT_T_Default 		= "objectives/objective_tutorial"

-- SOUND FILES
SOUND_MEDAL_OP			= "ui/stingers/medal_opportunity"
SOUND_OBJ_ADDED			= "ui/stingers/objective_added"
SOUND_OBJ_COMPLETED		= "ui/stingers/objective_completed"

-------------------------------------------------------------------------
-- Objective Helper functions - Outfit Style
-- @degnan
-- The Objective Helper functions are an attempt to centralize
-- all of the separate Objective functions into one set that 
-- can easily be added to or subtracted from as new functions
-- become available and old ones become obsolete.
-- Example of use: C:\WW2\BIA\Root\WW2\Data\Scar\Examples\Outfit_style_Objectives.scar
-------------------------------------------------------------------------

function RegisterObjectiveUpdate()

	g_SitRepObjective = nil
	
	Sound_PreCacheSound(SOUND_MEDAL_OP)
	Sound_PreCacheSound(SOUND_OBJ_ADDED)
	Sound_PreCacheSound(SOUND_OBJ_COMPLETED)
	
	Rule_Add(__UpdateObjectives, 10)
	RegisterObjectiveUpdate = nil
	__t_Objectives = {}
end

Scar_AddInit(RegisterObjectiveUpdate)

function __ObjectiveNothing()
end

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

--? @shortdesc 'Registers' an objective. Wrapper function for Objective_Create with a few other features.
--? @extdesc Includes pointers, labels and pings as defined by the objective table created in the main scar file.
--? @args LuaTable objTable
--? @result ObjectiveID
function Objective_Register(objTable)
	
	if objTable == nil then
		print("*** WARNING - objective table not valid! ***")
		return
	end
	
	if objTable.OnStart == nil then fatal("Objective_Register: objective missing OnStart function") end
	if objTable.SetupUI == nil then fatal("Objective_Register: objective missing SetupUI function") end
	if objTable.OnComplete == nil then fatal("Objective_Register: objective missing OnComplete function") end
	if objTable.OnFail == nil then fatal("Objective_Register: objective missing OnFail function") end
	--if objTable.OnSitRep == nil then fatal("Objective_Register: objective missing OnSitRep function") end
	
	-- setup empty tables if necessary
	if objTable.Pings == nil then objTable.Pings = {} end
	if objTable.Labels == nil then objTable.Labels = {} end
	if objTable.Arrows == nil then objTable.Arrows = {} end
	if objTable.Pointers == nil then objTable.Pointers = {} end
	if objTable.UIElements == nil then objTable.UIElements = {} end
	
	objTable.Counter = {}
	
	local icon = objTable.Icon
	
	-- set the icon according to the objective type
	if objTable.Type == OT_Primary then
		icon = IT_P_Default
	elseif objTable.Type == OT_Secondary then
		icon = IT_S_Default
	elseif objTable.Type == OT_Medal then
		icon = IT_M_Default
	end
	
--~ 	if icon == nil then
--~ 		icon = objTable.icon
--~ 	end
--~ 	if icon == nil then
--~ 		icon = ""
--~ 	end
	
	-- setup cue
	objTable.Cue = {icon = icon, sound = "General_alert"}
	
	objTable.bAlwaysShowTitle = false
	objTable.bAlwaysShowHintpoints = false

	if objTable.Type == nil then
		fatal("Objective_Register: objective needs a 'Type' field! Use OT_Primary, OT_Secondary or OT_Medal")
	end
	
	if objTable.Type == OT_Medal and objTable.MedalID == nil then
		fatal("Objective_Register: medal objectives need a 'MedalID' field! Use the MEDALS table in LuaConsts")
	end
	
	local medalID = objTable.MedalID
	if medalID == nil then medalID = 0 end
	
	objTable.ID = Obj_Create(objTable.Title, objTable.Description, icon, objTable.Type, medalID)
	
	-- register all callbacks
	Obj_SetObjectiveFunction(objTable.ID, FN_OnShow, __ObjectiveOnShowCallback)
	Obj_SetObjectiveFunction(objTable.ID, FN_OnSelect, __ObjectiveOnSelectCallback)
	if objTable.SitRep ~= nil then
		Obj_SetObjectiveFunction(objTable.ID, FN_OnActivate, __ObjectiveOnActivateCallback)
	end
	Obj_SetObjectiveFunction(objTable.ID, FN_OnCounterDisplay, __ObjectiveOnCounterDisplayCallback)
	Obj_SetObjectiveFunction(objTable.ID, 4, __ObjectiveLuaTableQueryCallback)
	
	table.insert(__t_Objectives, objTable)
	
	return objTable.ID
	
end


--? @shortdesc Shows an objective to the player and activates it
--? @extdesc Includes pointers, labels, pings and FOW as defined by the objective table created in the main scar file.
--? @args LuaTable objTable[, Boolean bShowTitle]
--? @result Void
function Objective_Start(objTable, bShowTitle)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_Start: Invalid Objective Table") end
	
	-- prevent multiple starts
	if objTable.started then
		return
	end
	
	-- grant obj
	Obj_SetVisible(objTable.ID, true)
	Obj_SetState(objTable.ID, OS_Incomplete)
	
	-- skip any events playing to allow the Objective to play
	if Event_IsAnyRunning() then
		--Event_Skip()
	end
	
	-- do sitreps
	local bDelayStart = false
	if objTable.SitRep ~= nil then
		
		if objTable.SitRep.Force == true then
			bDelayStart = true
			Objective_ForceSitRep(objTable)
		elseif objTable.SitRep.Movie ~= nil then
			Objective_AlertSitRep(objTable)
		end
		
	end
	
	-- perform the start tasks right away
	if bDelayStart == false then
		objTable.OnStart()
	end
	
	--[[ added to delay titlecards and other actions until after any events have played ]]
	objTable.showtitle = bShowTitle
	
	-- if not showing the title, then it's a silent start. do the UI setup right away instead of delaying it
	if bShowTitle == false then
		
		__DoObjectiveStartUI(objTable)
		
		objTable.Displayed = true -- don't do the delayed UI start
		
	end
	
	objTable.started = true

end
	

--? @shortdesc 'Completes' an objective. Wrapper function for Objective_SetState with a few other features. If you do not want the objective title to be shown on screen, pass in 'false' for bShowTitle
--? @extdesc Includes managing the blips and triggers the OnComplete() function as defined by the objective table created in the main scar file.
--? @args LuaTable objTable[, Boolean bShowTitle]
--? @result Void
function Objective_Complete(objTable, bShowTitle)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_Complete: Invalid Objective Table") end
	
	-- only complete it once
	if Obj_GetState(objTable.ID) == OS_Complete then
		return
	end
	
	-- hide all minimap blips once it's been completed
	__ShowObjectiveBlips(objTable, false)
	
	-- remove all UI elements
	while table.getn(objTable.UIElements) > 0 do
		Objective_RemoveUIElements(objTable, objTable.UIElements[1].ElementID)
	end
	
	--[[
	-- hide pings if objective was completed while viewing tactical map
	if objTable.Pings ~= nil then
		
		for k, v in pairs(objTable.Pings) do
			
			if v.ID ~= nil then
				
				TacticalMap_RemoveElement(v.ID)
				v.ID = nil
				
			end
		end
	end
	]]

	Obj_SetState(objTable.ID, OS_Complete)
	
	-- stop any active timers or counters
	Objective_StopTimer(objTable)
	Objective_StopCounter(objTable)
	
	-- award the medal if this a medal objective
	if objTable.Type == OT_Medal then
		if objTable.MedalID ~= nil then
			Scar_AwardMedal(objTable.MedalID)
		else
			fatal("Objective_Complete: Medal objective doesn't have MedalID field! No medal awarded...")
		end
	end
	
	-- perform the complete tasks
	objTable.OnComplete()
	
	-- flash title card
	if bShowTitle ~= false then
		
		Sound_Play2D(SOUND_OBJ_COMPLETED)
		
		-- some objs want a different closing message
		local objtitle = objTable.Title
		if objTable.TitleEnd ~= nil then
			objtitle = objTable.TitleEnd
		end
		
		if objTable.CustomComplete == nil then
			
			if objTable.Type == OT_Primary or objTable.Type == OT_Secondary then
				
				local title = Loc_FormatText(39300, objtitle)
				Util_MissionTitle( title )
				
			elseif objTable.Type == OT_Medal then
				
				local medalname = Scar_GetMedalName( objTable.MedalID )
				local title = Loc_FormatText(39304, medalname, objtitle)
				local gameTitle = function()
					CTRL.Game_TextTitleFade( title, 0, 3, 2, Scar_GetMedalImage( objTable.MedalID, true ) )
					CTRL.WAIT()
				end
				Util_StartIntel(gameTitle)
				
			else
				fatal("Objective_Complete: invalid objective type!")
			end
			
		-- special case for extended end rewards
		-- added by degnan for M06
		elseif objTable.CustomComplete ~= nil then
			
			-- standard NIS complete
			local CompleteNIS = function()
				
				-- letterbox
				CTRL.Game_Letterbox(true, 1)
				CTRL.WAIT()
				
				Player_GetAll(player1)
				SGroup_SetInvulnerable(sg_allsquads, true)
				
				if objTable.Type == OT_Primary or objTable.Type == OT_Secondary then
					
					local title = Loc_FormatText(39300, objtitle)
					CTRL.Game_TextTitleFade( title, 0, 4, 0, "" )
					CTRL.WAIT()
					
				elseif objTable.Type == OT_Medal then
					
					local medalname = Scar_GetMedalName( objTable.MedalID )
					local title = Loc_FormatText(39304, medalname, objtitle)
					CTRL.Game_TextTitleFade( title, 0, 3, 2, Scar_GetMedalImage( objTable.MedalID, true ) )
					CTRL.WAIT()
					
				else
					fatal("Objective_Complete: invalid objective type!")
				end
				
				-- do you want to focus on something?
				if objTable.CustomTarget ~= nil then
					-- set view
					_cameraPos = Camera_GetTargetPos()
					Camera_FocusOnPosition(objTable.CustomTarget, true)
					FOW_Enable(false)
					Camera_ResetToDefault()
				end
				
				
				
				-- second message 
				if objTable.CustomTitle ~= nil then
					
					CTRL.Game_TextTitleFade( objTable.CustomTitle, 0, 3, 2, "" )
					CTRL.WAIT()
					
				end
				
				SGroup_SetInvulnerable(sg_allsquads, false)
				
				CTRL.Game_Letterbox(false, 1)
					if _cameraPos ~= nil then
						Camera_FocusOnPosition(_cameraPos, true)
						_cameraPos = nil
						FOW_Enable(true)
					end
				CTRL.WAIT()
				
			end
			
			Event_Start(CompleteNIS, 0)
			
		end
		
	end
	
end

--? @shortdesc 'Fails' an objective. Wrapper function for Objective_SetState with a few other features.
--? @extdesc Includes managing the blips and triggers the OnFail() function as defined by the objective table created in the main scar file.
--? @args LuaTable objTable[, Boolean bShowTitle]
--? @result Void
function Objective_Fail(objTable, bShowTitle)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_Fail: Invalid Objective Table") end
	
	__ShowObjectiveBlips(objTable, false)
	
	Obj_SetState(objTable.ID, OS_Failed)
	
	-- perform the fail tasks
	objTable.OnFail()

	-- flash title card
	if bShowTitle ~= false then
		
		if objTable.Type == OT_Primary or objTable.Type == OT_Secondary then
			
			local title = Loc_FormatText(39301, objTable.Title)
			Util_MissionTitle( title )
			
		elseif objTable.Type == OT_Medal then
			
			local title = Loc_FormatText(39305, objTable.Title)
			Util_MissionTitle( title )
			
		else
			fatal("Objective_Start: invalid objective type!")
		end
		
	end
	
end

--? @shortdesc Shows or hides an objective from the UI and tactical map
--? @args LuaTable objective_table, Boolean on/off	
--? @result Void
function Objective_Show(objTable, show)
	
	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_Show: Invalid Objective Table") end
	
	Obj_SetVisible(objTable.ID, show)
	
end

--? @shortdesc Toggles minimap blips on or off.
--? @args LuaTable objective_table, Boolean on/off	
--? @result Void
function Objective_TogglePings( objTable, boolean )

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_TogglePings: Invalid Objective Table") end
	
	__ShowObjectiveBlips(objTable, boolean)

end

--? @shortdesc Adds multiple UI elements on one position. pass in 'false' for elements that you don't want. 'pos' can be group/entity/squad/marker. Setting 'worldHighlight' to true makes this entity/squad/group be highlighted in the world. objectiveArrowOffset is an optional offset applied to the arrow's position (you can specify a height offset or a 3D position offset).
--? @args LuaTable objTable, Position pos, Boolean ping, LocString hintpointText, Position arrowFrom, Boolean pointer[, Boolean worldHighlight, Float/Position objectiveArrowOffset]
--? @result ElementID
function Objective_AddUIElements(objTable, pos, ping, hintpointText, arrowFrom, pointer, worldHighlight, objectiveArrowOffset)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_AddUIElements: Invalid Objective Table") end
	
	if pos == nil then
		fatal("Objective_AddUIElements: 'pos' is nil!")
	end
	
	if ping == nil or hintpointText == nil or arrowFrom == nil or pointer == nil then
		fatal("Objective_AddUIElements: cannot pass in 'nil' arguments. use 'false' instead")
	end
	
	-- set default arrow offset
	if objectiveArrowOffset == nil then
		objectiveArrowOffset = 0
	end
	
	-- turn height offset into 3d offset
	if scartype(objectiveArrowOffset) == ST_NUMBER then
		objectiveArrowOffset = World_Pos(0,objectiveArrowOffset,0)
	end
	
	local elementTable = {}
	
	if ping ~= false then 		elementTable.PingID = Objective_AddPing(objTable, pos, 10) end
	--if labelText ~= false then 	elementTable.LabelID = Objective_AddLabel(objTable, pos, labelText) end
	--if arrowFrom ~= false then	elementTable.ArrowID = Objective_AddArrow(objTable, arrowFrom, pos) end
	--if pointer ~= false then	elementTable.PointerID = Objective_AddPointer(objTable, pos) end
	
	-- highlight this in the world?
	elementTable.HighlightedEntities = {} -- stores { eid, hintid }
	elementTable.HighlightedSquads = {} -- stores { sid, hintid }
	elementTable.HighlightedPositions = {} -- stores { pos, hintid }
	if worldHighlight == true then
		
		local type = scartype(pos)
		if type == ST_ENTITY then
			
			__HighlightEntity(objTable.ID, elementTable, pos, hintpointText, objectiveArrowOffset)
			
		elseif type == ST_SQUAD then
			
			__HighlightSquad(objTable.ID, elementTable, pos, hintpointText, objectiveArrowOffset)
			
		elseif type == ST_EGROUP then
			
			local _HighlightOneEntity = function(gid, idx, eid)
				__HighlightEntity(objTable.ID, elementTable, eid, hintpointText, objectiveArrowOffset)
			end
			EGroup_ForEachEx(pos, _HighlightOneEntity, true, false)
			
		elseif type == ST_SGROUP then
			
			local _HighlightOneSquad = function(gid, idx, sid)
				__HighlightSquad(objTable.ID, elementTable, sid, hintpointText, objectiveArrowOffset)
			end
			SGroup_ForEachEx(pos, _HighlightOneSquad, true, false)
			
		elseif type == ST_MARKER then
			
			-- GTA style "cone of light" ?
			__HighlightPosition(objTable.ID, elementTable, Marker_GetPosition(pos), hintpointText, objectiveArrowOffset)
			
		elseif type == ST_SCARPOS then
			
			-- ???
			__HighlightPosition(objTable.ID, elementTable, pos, hintpointText, objectiveArrowOffset)
			
		end
		
	end
	
	-- give this group an ID so it can be removed
	if objTable.NextElementID == nil then
		objTable.NextElementID = 1
	end
	
	elementTable.ElementID = objTable.NextElementID
	objTable.NextElementID = objTable.NextElementID + 1
	
	table.insert(objTable.UIElements, elementTable)
	
	return elementTable.ElementID
	
end

--? @shortdesc Removes a group of UI elements that were added by Objective_AddUIElements
--? @args LuaTable objTable, Integer elementID
--? @result Void
function Objective_RemoveUIElements(objTable, elementID)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_RemoveUIElements: Invalid Objective Table") end
	
	if objTable.UIElements == nil then
		return
	end
		
	for i = table.getn(objTable.UIElements), 1, -1 do
		
		local v = objTable.UIElements[i]
		if v.ElementID == elementID then
			
			-- try deleting each type of UI element (if nil, will fail silently)
			Objective_RemovePing(objTable, v.PingID)
			Objective_RemoveLabel(objTable, v.LabelID)
			Objective_RemoveArrow(objTable, v.ArrowID)
			Objective_RemovePointer(objTable, v.PointerID)
			
			-- remove world highlights
			for j = 1, table.getn(v.HighlightedEntities) do
				
				local entityid = v.HighlightedEntities[j].eid
				if Entity_IsValid(entityid) then
					local entity = Entity_FromWorldID(entityid)
					Obj_HighlightEntity(objTable.ID, entity, false)
				end
				
				local hintid = v.HighlightedEntities[j].hintid
				if hintid ~= nil then
					HintPoint_Remove(hintid)
				end
				
			end
			
			for j = 1, table.getn(v.HighlightedSquads) do
				
				local squadid = v.HighlightedSquads[j].sid
				if Squad_IsValid(squadid) then
					local squad = Squad_FromWorldID(squadid)
					if Squad_Count(squad) > 0 then
						Obj_HighlightSquad(objTable.ID, squad, false)
					end
				end
				
				local hintid = v.HighlightedSquads[j].hintid
				if hintid ~= nil then
					HintPoint_Remove(hintid)
				end
				
			end
			
			for j = 1, table.getn(v.HighlightedPositions) do
				
				local pos = v.HighlightedPositions[j].pos
				Obj_HighlightPosition(objTable.ID, pos, false)
				
				local hintid = v.HighlightedPositions[j].hintid
				if hintid ~= nil then
					HintPoint_Remove(hintid)
				end
				
			end
			
			-- remove position decorator (if any)
			--if v.PositionDecoratorID ~= nil then
			--	UI_DeletePositionDecorator(v.PositionDecoratorID)
			--end
			
			-- forget all about it
			table.remove(objTable.UIElements, i)
			return
			
		end
		
	end
	
end

--? @shortdesc Adds a tactical map ping to an objective
--? @result PingID
--? @args LuaTable objectiveTable, Position pos, Float radius
function Objective_AddPing(objTable, pos, radius)
	
	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_AddPing: Invalid Objective Table") end
	
	local newPing = { pos = pos, radius = radius }
	table.insert(objTable.Pings, newPing)
	
	if Objective_IsStarted(objTable) and Obj_GetState(objTable.ID) == OS_Incomplete then
		
		-- show it on minimap right away
		__ShowSingleBlip(objTable, newPing, true)
		
	end
	
	return newPing.BlipID
end
 
--? @shortdesc Adds a tactical map label to an objective
--? @result LabelID
--? @args LuaTable objectiveTable, Position pos, LocString text
function Objective_AddLabel(objTable, pos, text)
	
	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_AddLabel: Invalid Objective Table") end
	
	local newLabel = { pos = pos, text = text }
	table.insert(objTable.Labels, newLabel)
	
	-- show it right away
	if Objective_IsStarted(objTable) and Obj_GetState(objTable.ID) == OS_Incomplete then
		__ObjectiveOnSelectCallback(objTable.ID)
	end
	
	return newLabel.ID
	
end
 
--? @shortdesc Adds a tactical map arrow to an objective
--? @result ArrowID
--? @args LuaTable objectiveTable, Position from, Position to
function Objective_AddArrow(objTable, from, to)
	
	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_AddArrow: Invalid Objective Table") end
	
	local newArrow = { from = from, to = to }
	table.insert(objTable.Arrows, newArrow)
	
	-- show it right away
	if Objective_IsStarted(objTable) and Obj_GetState(objTable.ID) == OS_Incomplete then
		__ObjectiveOnSelectCallback(objTable.ID)
	end
	
	return newArrow.ID
	
end
 
--? @shortdesc Adds a tactical map pointer to an objective
--? @result PointerID
--? @args LuaTable objectiveTable, Position pos
function Objective_AddPointer(objTable, pos)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_AddPointer: Invalid Objective Table") end
	
	local newPointer = { pos = pos }
	table.insert(objTable.Pointers, newPointer)
	
	-- show it right away
	if Objective_IsStarted(objTable) and Obj_GetState(objTable.ID) == OS_Incomplete then
		__ObjectiveOnSelectCallback(objTable.ID)
	end
	
	return newPointer.ID
	
end

--? @shortdesc Removes a tactical map ping from an objective
--? @result Void
--? @args LuaTable objectiveTable, Integer PingID
function Objective_RemovePing(objTable, pingID)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_RemovePing: Invalid Objective Table") end
	
	-- delete the minimap blip too
	-- uhhh, BlipID is stored in the same table entry as the PingID, so we must find it manually in order to retrieve the BlipID
	if objTable.Pings ~= nil then
		
		-- we don't usually remove UI elements from the tacmap (it removes them on its own, and only asks us when to add them back)
		-- but in this case if this function is being called while the tacmap is on screen, we want to remove the ping right away
		__RemoveTacticalMapElement(objTable.Pings, pingID)
		
		for i = 1, table.getn(objTable.Pings) do
			
			-- find the entry for the ping, and it will have the blip ID as well
			local entry = objTable.Pings[i]
			if entry.BlipID ~= nil and entry.BlipID == pingID then
				
				UI_DeleteMinimapBlip(entry.BlipID)
				table.remove(objTable.Pings, i)
				break
				
			end
		end
		
	end
	
end

--? @shortdesc Removes a tactical map label from an objective
--? @result Void
--? @args LuaTable objectiveTable, Integer LabelID
function Objective_RemoveLabel(objTable, labelID)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_RemoveLabel: Invalid Objective Table") end
	
	__RemoveTacticalMapElement(objTable.Labels, labelID)
	
end

--? @shortdesc Removes a tactical map arrow from an objective
--? @result Void
--? @args LuaTable objectiveTable, Integer ArrowID
function Objective_RemoveArrow(objTable, arrowID)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_RemoveArrow: Invalid Objective Table") end
	
	__RemoveTacticalMapElement(objTable.Arrows, arrowID)
	
end

--? @shortdesc Removes a tactical map pointer from an objective
--? @result Void
--? @args LuaTable objectiveTable, Integer PointerID
function Objective_RemovePointer(objTable, pointerID)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_RemovePointer: Invalid Objective Table") end
	
	__RemoveTacticalMapElement(objTable.Pointers, pointerID)
	
end

--? @shortdesc Returns whether an objective is complete
--? @args LuaTable objTable
--? @result Boolean
function Objective_IsComplete(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_IsComplete: Invalid Objective Table") end
	
	if Obj_GetState(objTable.ID) == OS_Complete then
		return true
	else
		return false
	end

end

--? @shortdesc Returns whether an objective is failed
--? @args LuaTable objTable
--? @result Boolean
function Objective_IsFailed(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_IsFailed: Invalid Objective Table") end
	
	if Obj_GetState(objTable.ID) == OS_Failed then
		return true
	else
		return false
	end
	
end

--? @shortdesc Returns whether an objective has been started. Completed objectives will return true.
--? @args LuaTable objTable
--? @result Void
function Objective_IsStarted(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_IsStarted: Invalid Objective Table") end
	
	if objTable.started == true then
		return true
	else
		return false
	end
	
end

--? @shortdesc Returns whether all primary objectives have been completed.
--? @args none
--? @result Boolean
function Objective_AreAllPrimaryObjectivesComplete()

	local bAnyPrimaryObjectives = false
	
	for k, v in pairs(__t_Objectives) do
		
		if v.Type == OT_Primary then
			
			bAnyPrimaryObjectives = true
			if not Objective_IsComplete(v) then
				return false
			end
			
		end
		
	end
	
	-- if there were not any primary objectives found, then treat it as not all of them being completed
	return bAnyPrimaryObjectives
	
end

--? @shortdesc Updates the title and description for the objective. If you only want to set one of them, pass in nil for the other
--? @args LuaTable objTable, LocString title, LocString description[, Boolean bShowTitle]
--? @result Void
function Objective_UpdateText(objTable, title, description, bShowTitle)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_UpdateText: Invalid Objective Table") end
	
	if title ~= nil then
		Obj_SetTitle(objTable.ID, title)
		
		if bShowTitle ~= false then
			local title = Loc_FormatText(39307, title)
			Obj_CreatePopup (objTable.ID, title)
			--Util_MissionTitle( title )
		end
	end
	
	if description ~= nil then
		Obj_SetDescription(objTable.ID, description)
	end
	
end

COUNT_UP = 0
COUNT_DOWN = 1

--? @shortdesc Starts a timer that is associated with this objective in the UI. Use COUNT_DOWN or COUNT_UP for the 'direction' parameter
--? @args LuaTable objTable, Integer direction[, Float initialTime, Float flashThreshold]
--? @result Void
function Objective_StartTimer(objTable, direction, initialTime, flashThreshold)
	
	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_StartTimer: Invalid Objective Table") end
	
	local previousFlashID = objTable.Counter.FlashID
	Objective_StopTimer(objTable)
	objTable.Counter.TimerID = objTable.ID + 1000
	objTable.Counter.FlashThreshold = flashThreshold
	objTable.Counter.FlashID = previousFlashID
	
	if direction == COUNT_UP then
		
		objTable.Counter.Type = COUNTER_TimerIncreasing
		Timer_Start(objTable.Counter.TimerID, 9999)
		if initialTime ~= nil then
			Timer_Advance(objTable.Counter.TimerID, initialTime)
		end
		
	elseif direction == COUNT_DOWN then
		
		objTable.Counter.Type = COUNTER_TimerDecreasing
		Timer_Start(objTable.Counter.TimerID, initialTime)
		
	else
		fatal("Objective_StartTimer: 'direction' is not valid; use COUNT_UP or COUNT_DOWN")
	end
	
end


--? @shortdesc Pauses the objective's timer. If a timer has not been set, it does nothing.
--? @args LuaTable objTable
--? @result Void
function Objective_PauseTimer(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_PauseTimer: Invalid Objective Table") end
	
	if Objective_IsTimerSet(objTable) then
		Timer_Pause(objTable.Counter.TimerID)
	end

end


--? @shortdesc Resume the objective's timer. If a timer has not been set, it does nothing.
--? @args LuaTable objTable
--? @result Void
function Objective_ResumeTimer(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_ResumeTimer: Invalid Objective Table") end
	
	if Objective_IsTimerSet(objTable) then
		Timer_Resume(objTable.Counter.TimerID)
	end

end


--? @shortdesc Stops the objective's timer. If a timer has not been set, it does nothing.
--? @args LuaTable objTable
--? @result Void
function Objective_StopTimer(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_StopTimer: Invalid Objective Table") end
	
	if Objective_IsTimerSet(objTable) then
		Timer_End(objTable.Counter.TimerID)
		objTable.Counter = {}
	end

end

--? @shortdesc Returns the amount of seconds on the timer (time remaining or time elapsed, based on the type of timer used)
--? @args LuaTable objTable
--? @result Integer
function Objective_GetTimerSeconds(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_GetTimerSeconds: Invalid Objective Table") end
	
	if objTable.Counter.Type == COUNTER_TimerIncreasing then
		
		return Timer_GetElapsed(objTable.Counter.TimerID)
		
	elseif objTable.Counter.Type == COUNTER_TimerDecreasing then
		
		return Timer_GetRemaining(objTable.Counter.TimerID)
		
	else	
		fatal("Objective_GetTimerSeconds: no timer set!")
	end
	
end

--? @shortdesc Returns true if a timer has been set for this objective
--? @args LuaTable objTable
--? @result Boolean
function Objective_IsTimerSet(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_IsTimerSet: Invalid Objective Table") end
	
	if objTable.Counter == nil then
		return false
	end
	
	return objTable.Counter.Type == COUNTER_TimerIncreasing or objTable.Counter.Type == COUNTER_TimerDecreasing
end

--? @shortdesc Sets a counter that is associated with this objective in the UI. You can provide a 'maximum' so that it shows up as "1 of 5"
--? @args LuaTable objTable, Float current, Float maximum
--? @result Void
function Objective_SetCounter(objTable, current, maximum)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_SetCounter: Invalid Objective Table") end
	
	Objective_StopTimer(objTable)
	
	objTable.Counter.Count = current
	
	if maximum == nil then
		-- basic counter
		objTable.Counter.Type = COUNTER_Count
	else
		-- counter with maximum (ex: 2 of 5)
		objTable.Counter.Type = COUNTER_CountUpTo
		objTable.Counter.Maximum = maximum
	end
	
end

--? @shortdesc Stops the objective's counter. If a counter has not been set, it does nothing.
--? @args LuaTable objTable
--? @result Void
function Objective_StopCounter(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_StopCounter: Invalid Objective Table") end
	
	if Objective_IsCounterSet(objTable) then
		objTable.Counter = {}
	end
	
end

--? @shortdesc Returns the current count associated with this objective.
--? @args LuaTable objTable
--? @result Integer
function Objective_GetCounter(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_GetCounter: Invalid Objective Table") end

	if Objective_IsTimerSet(objTable) then
		return objTable.Counter.Count
	else
		fatal("Objective_GetCounter: no counter set!")
	end
	
end

--? @shortdesc Returns true if a counter has been set for this objective
--? @args LuaTable objTable
--? @result Boolean
function Objective_IsCounterSet(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_IsCounterSet: Invalid Objective Table") end
	
	if objTable.Counter == nil then
		return false
	end
	
	return objTable.Counter.Type == COUNTER_Count or objTable.Counter.Type == COUNTER_CountUpTo
	
end

--? @shortdesc Drops an event cue for player 1, allowing them to access the sitrep for the objective
--? @args LuaTable objTable
--? @result Void
function Objective_AlertSitRep(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_AlertSitRep: Invalid Objective Table") end
	
	Obj_AlertSitRep(player1, objTable.ID)
	
end

--? @shortdesc Brings you to the tactical map screen and plays the sitrep for this objective
--? @args LuaTable objTable
--? @result Void
function Objective_ForceSitRep(objTable)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_ForceSitRep: Invalid Objective Table") end
	
	if UI_IsTacticalMapShown() then
		TacticalMap_StartSitRep(objTable.ID)
	else
		g_SitRepObjective = objTable
		Util_StartIntel(__SitRepTransitionIn)
	end
	
	
	objTable.ForcingSitRep = true
end

--? @shortdesc Sets whether this objective always shows detailed text, the HUD arrow, or the hintpoints. There can only be one objective at a time that forces the HUD arrow to show up. If you pass in 'nil' for hud_arrow then its behavior is not affected.
--? @args LuaTable objTable, Boolean title, Boolean hud_arrow, Boolean hintpoints
--? @result Void
function Objective_SetAlwaysShowDetails(objTable, title, hud_arrow, hintpoints)

	-- do some type checking
	if scartype(objTable) ~= ST_TABLE then fatal("Objective_SetAlwaysShowDetails: Invalid Objective Table") end
	
	objTable.bAlwaysShowTitle = title
	objTable.bAlwaysShowHintpoints = hintpoints
	
	if hud_arrow == true then
		UI_ForceHudArrowOnObjective(objTable.ID)
	elseif hud_arrow == false then
		UI_ForceHudArrowOnObjective(-1)
	end
	
end

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

--
-- private functions
--

function __ObjectiveOnShowCallback(id)
	
	local objTable = __FindObjectiveTable(id)
	
	-- only show pings for incomplete objectives
	if Obj_GetState(objTable.ID) ~= OS_Incomplete then
		return
	end
	
	if objTable.Pings ~= nil then
		
		-- show all pings for this objective
		for k, v in pairs(objTable.Pings) do
			
			--local pos = Util_GetPosition(v.pos)
			local radius = v.radius
			if radius == nil then
				radius = 0
			end
			
			if v.TacMapID == nil or not TacticalMap_HasElement(v.TacMapID) then
				v.TacMapID = TacticalMap_AddPing( v.pos, -1, __GetBlipType(objTable) )
			end
			
		end
		
	end
	
end

function __ObjectiveOnSelectCallback(id)

	local objTable = __FindObjectiveTable(id)
	
	-- show all labels, arrows and pointers for this objective
	
	if objTable.Labels ~= nil then
		for k, v in pairs(objTable.Labels) do
			
			local pos = Util_GetPosition(v.pos)
			if v.ID == nil or not TacticalMap_HasElement(v.ID) then
				v.ID = TacticalMap_AddMessage(id, FN_OnSelect, pos, v.text)
			end
			
		end
	end

	if objTable.Arrows ~= nil then
		for k, v in pairs(objTable.Arrows) do
			
			local from = Util_GetPosition(v.from)
			local to = Util_GetPosition(v.to)
			if v.ID == nil or not TacticalMap_HasElement(v.ID) then
				v.ID = TacticalMap_AddArrow(id, FN_OnSelect, from, to)
			end
			
		end
	end

	if objTable.Pointers ~= nil then
		for k, v in pairs(objTable.Pointers) do
			
			local pos = Util_GetPosition(v.pos)
			if v.ID == nil or not TacticalMap_HasElement(v.ID) then
				v.ID = TacticalMap_AddPointer(id, FN_OnSelect, pos)
			end
			
		end
	end
	
end

function __ObjectiveOnActivateCallback(id)

	local objTable = __FindObjectiveTable(id)
	
	-- start sitrep movie and event
	if objTable.SitRep ~= nil and objTable.SitRep.Movie ~= nil then
		
		SitRep_PlayMovie(objTable.SitRep.Movie)
		
		-- apply any other sitrep parameters here (music?)
		
		-- parse the speech timing table so designers have an easier time setting up speech timings
		if objTable.SitRep.SpeechTiming ~= nil then
			g_SitRepObjective = objTable
			Event_Start(__SitRepEvent, 0)
		end
		
	end
	
end

function __SitRepTransitionIn()

	if Game_IsLetterboxed() == false then
		CTRL.Game_Letterbox(true, 1)
		CTRL.WAIT()
	end
	
	Sound_SetVolume("Speech", 0.0, 1.5)
	Sound_SetVolume("SFXmaster", 0.0, 1.5)
	CTRL.Game_FadeToBlack(FADE_OUT, 1.5)
	CTRL.WAIT()

	UI_ShowTacticalMap()	
	TacticalMap_StartSitRep(g_SitRepObjective.ID)
	
	-- fade up underneath the tacmap, in case the user exits the tacmap at any point. otherwise he'd end up looking at a black screen
	Game_FadeToBlack(FADE_IN, 0)
	Game_Letterbox(false, 0)
	
	-- __SitRepEvent will get triggered via the __ObjectiveOnActivateCallback callback
	-- it will play the timed sitrep speech
	
end

function __SitRepEvent()

	local path = ""
	if g_MissionSpeechPath ~= nil then
		path = g_MissionSpeechPath
	end
	
	-- play speech entries one at a time, according to the timing set up in the objective table
	for i = 1, table.getn(g_SitRepObjective.SitRep.SpeechTiming) do
		
		local entry = g_SitRepObjective.SitRep.SpeechTiming[i]
		local previousEntry = g_SitRepObjective.SitRep.SpeechTiming[i - 1]
		
		-- convert from absolute time to relative time (unless it's the first speech being played)
		local delay = entry[1]
		if previousEntry ~= nil then
			delay = delay - previousEntry[1]
		end
		
		--print("__SitRepEvent: [" .. i .. "] delay: " .. delay)
		-- delay until it's time to play
		if delay > 0 then
			CTRL.Event_Delay(delay)
			CTRL.WAIT()
		end
		
		-- play the speech
		if i < table.getn(g_SitRepObjective.SitRep.SpeechTiming) then
			-- not the last speech
			-- don't wait on it (the delay between speech is explicitly set above)
			Subtitle_EndCurrentSpeech()
			SitRep_PlaySpeech(entry[2].icon, entry[3], false, false, false, false, path)
		else
			-- the last speech
			-- wait on it (we want to delay the end of the sitrep event until the last speech has *ended*)
			Subtitle_EndCurrentSpeech()
			CTRL.SitRep_PlaySpeech(entry[2].icon, entry[3], false, false, false, false, path)
			CTRL.WAIT()
			
			-- brief delay
			CTRL.Event_Delay(2)
			CTRL.WAIT()
			
			-- stop the sitrep during a rule, not an event. this is to get around the code that skips all events
			-- when the tacmap gets hidden
			if g_SitRepObjective.ForcingSitRep == true then
				g_SitRepObjective.bRequestSitRepStop = true
			end
			
			
		end
		
	end
	
end

function __SitRepWaitForTransition()

	-- wait for sitrep to end and for the tacmap to fade back in on top of it
	CTRL.Event_Delay(2)
	CTRL.WAIT()
	
	-- right now, the game screen underneath is faded up in case the user manually exits the tacmap.
	-- but now, we're just about to force an exit from the tacmap, so we'd like to fade up smoothly.
	-- fade to black instantly in preparation
--	Game_Letterbox(true, 0)
--	Game_FadeToBlack(FADE_OUT, 0)

	g_SitRepObjective.bRequestTacMapClose = true
	

end

function __SitRepTransitionOut()
	Game_FadeToBlack(FADE_OUT, 0)
	CTRL.Event_Delay(0.1)
	CTRL.WAIT()
	
	CTRL.Game_Letterbox(false, 0)
	CTRL.Game_FadeToBlack(FADE_IN, 1.5)
	CTRL.WAIT()
	Sound_SetVolumeDefault("Speech", 0)
	Sound_SetVolumeDefault("SFXmaster", 0)			
	
	g_SitRepObjective.bRequestOnStartCall = true
	
end

function __ObjectiveOnCounterDisplayCallback(id)

	local objTable = __FindObjectiveTable(id)
	local counter = objTable.Counter
	if counter == nil or counter.Type == nil then
		return 0
	end
	
	-- "Error"
	local strResult = 2700
	
	if Objective_IsTimerSet(objTable) then
	--if counter.Type == COUNTER_TimerIncreasing or counter.Type == COUNTER_TimerDecreasing then
		
		strResult = Loc_FormatTime(math.floor(Objective_GetTimerSeconds(objTable)), false, false)
		
	elseif counter.Type == COUNTER_Count then
		
		strResult = Loc_ConvertNumber(counter.Count)
		
	elseif counter.Type == COUNTER_CountUpTo then
		
		local strNumCurrent = Loc_ConvertNumber(counter.Count)
		local strNumMax = Loc_ConvertNumber(counter.Maximum)
		strResult = Loc_FormatText(39320, strNumCurrent, strNumMax)
		
	end
	
	if strResult == 2700 then
		print("__ObjectiveOnCounterDisplayCallback (objectives): unexpected counter type: " .. counter.Type)
	end
	
	return strResult
	
end

function __ObjectiveLuaTableQueryCallback(id, arg1)

	local objTable = __FindObjectiveTable(id)
	
	if arg1 == "always_show_title" then
		if objTable.bAlwaysShowTitle == true then return 1 else return 0 end
	elseif arg1 == "always_show_hintpoints" then
		if objTable.bAlwaysShowHintpoints == true then return 1 else return 0 end
	end
	
	return -1
	
end

function __UpdateObjectives()

	for k, v in pairs(__t_Objectives) do
		
		local objTable = v
		
		if 	objTable.started == true and objTable.Displayed ~= true 
		and Event_IsAnyRunning() == false and UI_IsTacticalMapShown() == false then
			
			objTable.Displayed = true
			
			__DoObjectiveStartUI(objTable)
			
		end
		
		-- update flashing timers
		if v.Counter.FlashThreshold then
			
			-- comparison functions. they return whether the threshold has been reached
			local _TimerIncComparison = function(objTable, val)
				return val >= objTable.Counter.FlashThreshold
			end
			
			local _TimerDecComparison = function(objTable, val)
				return val <= objTable.Counter.FlashThreshold
			end
			
			local comp
			local counter
			if v.Counter.Type == COUNTER_TimerIncreasing then
				
				comp = _TimerIncComparison
				counter = Objective_GetTimerSeconds(v)
				
			elseif v.Counter.Type == COUNTER_TimerDecreasing then
				
				comp = _TimerDecComparison
				counter = Objective_GetTimerSeconds(v)
				
			elseif v.Counter.Type == COUNTER_Count then
				
			elseif v.Counter.Type == COUNTER_CountUpTo then
				
			end
			
			if comp and comp(v, counter) then
				
				-- we hit the threshold, so start flashing using widget animation
				if v.Counter.FlashID == nil then
					v.Counter.FlashID = UI_FlashObjectiveCounter(v.ID, -1)
				end
				
			elseif comp and not comp(v, counter) then
				
				-- if the counter is not beyond the threshold anymore, stop flashing
				if v.Counter.FlashID ~= nil then	
					UI_StopFlashing(v.Counter.FlashID)
					v.Counter.FlashID = nil
				end
				
			end
			
		end
		
	end


	if( g_SitRepObjective ~= nil ) then
	
		if g_SitRepObjective.bRequestSitRepStop == true then
			
			g_SitRepObjective.bRequestSitRepStop = nil
				
			-- handle user exiting tacmap manually
			if UI_IsTacticalMapShown() then
				
				TacticalMap_StopSitRep()
				Util_StartIntel(__SitRepWaitForTransition)
				
			else
				
				-- we still need to call OnStart
				g_SitRepObjective.bRequestOnStartCall = true
				
			end
			
		end
		
		if g_SitRepObjective.bRequestTacMapClose == true then
			
			g_SitRepObjective.bRequestTacMapClose = nil
			
			-- handle user exiting tacmap manually
			if UI_IsTacticalMapShown() then
				
				UI_HideTacticalMap()
				Util_StartIntel(__SitRepTransitionOut)
				
			else
				
				-- we still need to call OnStart
				g_SitRepObjective.bRequestOnStartCall = true
				
			end
			
		end
		
		if g_SitRepObjective.bRequestOnStartCall == true then
			Sound_SetVolumeDefault("Speech", 0)
			Sound_SetVolumeDefault("SFXmaster", 0)			
			
			g_SitRepObjective.bRequestOnStartCall = nil
			g_SitRepObjective.ForcingSitRep = nil
			g_SitRepObjective.OnStart()
			
			g_SitRepObjective = nil
			
		end
	end
		
end

function __FindObjectiveTable(id)

	for k, v in pairs(__t_Objectives) do
		if v.ID == id then
			return v
		end
	end
	
	-- is this redundant in Lua?
	return nil
	
end

function __GetBlipType(objTable)

	if objTable.Type == OT_Primary then
		return BT_ObjectivePrimary
	elseif objTable.Type == OT_Secondary then
		return BT_ObjectiveSecondary
	elseif objTable.Type == OT_Medal then
		return BT_ObjectiveMedal
	end
	
	return BT_GeneralPing
	
end

function __ShowSingleBlip(objTable, ping, bShow)

	if bShow == true then
		
		-- only create it if it doesn't exist yet
		if ping.BlipID == nil then
			ping.BlipID = UI_CreateMinimapBlip(ping.pos, -1, __GetBlipType(objTable))
		end
		
	else
		
		-- only delete it if it already exists
		if ping.BlipID ~= nil then
			UI_DeleteMinimapBlip(ping.BlipID)
			ping.BlipID = nil
		end
		
	end
	
end

function __ShowObjectiveBlips(objTable, bShow)

	if objTable.Pings ~= nil then
		
		for k, v in pairs(objTable.Pings) do
			__ShowSingleBlip(objTable, v, bShow)
		end
		
	end
	
end

function __RemoveTacticalMapElement(elementTable, elementID)

	if elementTable == nil or elementID == nil or not TacticalMap_HasElement(elementID) then
		--print("__RemoveTacticalMapElement: early exit")
		return
	end
	
	for k, v in pairs(elementTable) do
		
		if v.ID == elementID then
			
			elementTable[k] = nil
			TacticalMap_RemoveElement(elementID)
			return
			
		end
		
	end
	
end

function __HighlightEntity(objID, elementTable, entity, hintpoint, arrowOffset)

	Obj_HighlightEntity(objID, entity, true)
	
	local hintpointLocID = hintpoint
	if hintpointLocID == false then
		hintpointLocID = 0
	end
	
	-- the "3d arrow" is tied to a hintpoint, so if no hintpoint is requested we just create one anyway and hide it.
	local hintid = HintPoint_AddToEntity(entity, hintpoint ~= false, __ObjectiveNothing, hintpointLocID, arrowOffset, objID)
	
	table.insert(elementTable.HighlightedEntities, { eid = Entity_GetGameID(entity), hintid = hintid } )

end

function __HighlightSquad(objID, elementTable, squad, hintpoint, arrowOffset)

	if Squad_Count(squad) > 0 then
		Obj_HighlightSquad(objID, squad, true)
		
		local hintpointLocID = hintpoint
		if hintpointLocID == false then
			hintpointLocID = 0
		end
		
		-- the "3d arrow" is tied to a hintpoint, so if no hintpoint is requested we just create one anyway and hide it.
		local hintid = HintPoint_AddToSquad(squad, hintpoint ~= false, __ObjectiveNothing, hintpointLocID, arrowOffset, objID)
		
		table.insert(elementTable.HighlightedSquads, { sid = Squad_GetGameID(squad), hintid = hintid } )
	end
	
end

function __HighlightPosition(objID, elementTable, pos, hintpoint, arrowOffset)

	Obj_HighlightPosition(objID, pos, true)

	local hintpointLocID = hintpoint
	if hintpointLocID == false then
		hintpointLocID = 0
	end
	
	-- the "3d arrow" is tied to a hintpoint, so if no hintpoint is requested we just create one anyway and hide it.
	local hintid = HintPoint_AddToPosition(pos, hintpoint ~= false, __ObjectiveNothing, hintpointLocID, arrowOffset, objID)
	
	table.insert(elementTable.HighlightedPositions, { pos = pos, hintid = hintid } )
	
end

function __DoObjectiveStartUI(objTable)

	--[[ moved from Objective_Start ]]
	
	-- setup initial UI elements
	objTable.SetupUI()
	
	-- add blips
	__ShowObjectiveBlips(objTable, true)
	
	-- FOW
	if objTable.FOW ~= nil then
		
		for m, p in pairs(objTable.FOW) do
			
			local duration = p.duration
			if duration == nil then duration = 20 end
			
			local type = scartype(p.target)
			if type == ST_ENTITY then
				FOW_RevealEntity(p.target, duration)
			elseif type == ST_MARKER then
				FOW_RevealMarker(p.target, duration)
			elseif type == ST_SCARPOS then
				error("Objective_Start: positions are not valid FOW reveal types (use marker instead)")
			elseif type == ST_EGROUP then
				FOW_RevealEGroup(p.target, duration)
			elseif type == ST_SGROUP then
				FOW_RevealSGroup(p.target, duration)
			elseif type == ST_SQUAD then
				FOW_RevealSquad(p.target, duration)
			else
				error("Objective_Start: invalid FOW type")
			end
			
		end
		
	end
	
	local bShowTitle = objTable.showtitle
	
	if bShowTitle ~= false then
		
		local title = objTable.Title
		if objTable.DisplayTitleStart ~= nil then
			title = objTable.DisplayTitleStart
		end
		
		-- flash title card (replace with localized text)
		if objTable.Type == OT_Primary then
			
--			local title = Loc_FormatText(39302, objTable.Title)
			Obj_CreatePopup(objTable.ID, title)
			Sound_Play2D(SOUND_OBJ_ADDED)
			
		elseif objTable.Type == OT_Secondary then
			
--			local title = Loc_FormatText(39303, objTable.Title)
			Obj_CreatePopup(objTable.ID, title)
			Sound_Play2D(SOUND_OBJ_ADDED)
			
		elseif objTable.Type == OT_Medal then
			
--			local title = Loc_FormatText(39306, objTable.Title)
			Obj_CreatePopup(objTable.ID, title)
			Sound_Play2D(SOUND_MEDAL_OP)
			
		else
			fatal("Objective_Start: invalid objective type!")
		end
	end


end
