Scriptname _00E_GypsyMinstrelsControlScript extends Quest Conditional

Int Property WAYPOINT_PERFORM_MARKET = 0 AutoReadOnly
Int Property WAYPOINT_TRAVEL_INN     = 1 AutoReadOnly
Int Property WAYPOINT_SANDBOX_INN    = 2 AutoReadOnly
Int Property WAYPOINT_TRAVEL_MARKET  = 3 AutoReadOnly

Float Property SAFE_TELEPORT_DISTANCE = 4000.0 AutoReadOnly

; TIME_PERFORMANCE_START must be less than TIME_PERFORMANCE_END
; TIME_PERFORMANCE_START and TIME_PERFORMANCE_END must be greater than 0
; Otherwise the curHour check in UpdateWaypoint must be rewritten
Float Property TIME_PERFORMANCE_START = 7.0 AutoReadOnly
Float Property TIME_PERFORMANCE_END = 23.0 AutoReadOnly

Int Property CurrentWaypoint Auto Hidden Conditional

ReferenceAlias Property Alias_LutePlayer Auto
ReferenceAlias Property Alias_Dancer Auto

GlobalVariable Property GameHour Auto

Cell Property InnCell Auto
ObjectReference Property TipBasket Auto
ObjectReference Property TempStorageMarker Auto
Actor Property PlayerRef Auto
MusicType Property ArkMarketGypsyPerformanceSilence Auto
Keyword Property LinkCustom01 Auto
Keyword Property LinkPerformanceSpot Auto
Quest Property MQ12b Auto
Quest Property MQ14 Auto
ObjectReference Property MQ12c_Debris_Level_02_Linker Auto


;=====================================================================================
;              							EVENTS
;=====================================================================================

Event OnInit()
	CurrentWaypoint = -1 ; Force update
	RegisterForSingleUpdate(3.0) ; Give a few seconds for the game to warm up at the start
EndEvent

Event OnUpdate()
	UpdateWaypoint()
EndEvent

Event OnUpdateGameTime()
	UpdateWaypoint()
EndEvent


;=====================================================================================
;              							FUNCTIONS
;=====================================================================================

Function TriggerUpdate()
	RegisterForSingleUpdate(1.0)
EndFunction

Bool IsTipBasketHidden = False
Bool WaypointUpdateLocked = False

; Called from OnUpdate, OnUpdateGameTime and from end events of travel packages
Function UpdateWaypoint()
	While WaypointUpdateLocked
		Utility.Wait(0.5)
	EndWhile
	WaypointUpdateLocked = True

	If MQ14.GetCurrentStageID() >= 40
		; When we reach the endgame in MQ14, the schedule does not matter anymore
		SetNewWaypoint(WAYPOINT_SANDBOX_INN, True, -1.0)
	ElseIf MQ12b.GetCurrentStageID() >= 45 && MQ12c_Debris_Level_02_Linker.IsDisabled()
		; When the siege of Ark starts, force the minstrels to the inn and keep them there while the streets are cleaned up from debris and bodies.
		Float nextUpdateTime = TIME_PERFORMANCE_START - GameHour.GetValue()
		If nextUpdateTime < 0.0
			nextUpdateTime += 24.0
		EndIf
		SetNewWaypoint(WAYPOINT_SANDBOX_INN, True, nextUpdateTime)
	Else
		; Routine time of day update
		Float curHour = GameHour.GetValue()
		If curHour >= TIME_PERFORMANCE_START && curHour < TIME_PERFORMANCE_END
			RoutineWaypointUpdate(WAYPOINT_PERFORM_MARKET, WAYPOINT_TRAVEL_MARKET, curHour - TIME_PERFORMANCE_START, TIME_PERFORMANCE_END - curHour)
		ElseIf curHour >= TIME_PERFORMANCE_END
			RoutineWaypointUpdate(WAYPOINT_SANDBOX_INN, WAYPOINT_TRAVEL_INN, curHour - TIME_PERFORMANCE_END, TIME_PERFORMANCE_START - curHour + 24.0)
		Else ; curHour < TIME_PERFORMANCE_START
			RoutineWaypointUpdate(WAYPOINT_SANDBOX_INN, WAYPOINT_TRAVEL_INN, curHour - TIME_PERFORMANCE_END + 24.0, TIME_PERFORMANCE_START - curHour)
		EndIf
	EndIf

	WaypointUpdateLocked = False
EndFunction

Function RoutineWaypointUpdate(Int iCampWaypoint, Int iTravelWaypoint, Float fMissedStartByHours, Float fNextUpdateTime)
	Actor luter = Alias_LutePlayer.GetActorReference()
	Bool bTravel = False
	Bool bTeleport = False

	; If the lute player is not in the intended "camp" location, travel to it.
	; But if the start time is missed by a significant margin (probably because of the player sleeping or a quest time skip), 
	; teleport the minstrels to the "camp" directly, but only if it happens out of sight of the player.
	If iCampWaypoint == WAYPOINT_PERFORM_MARKET
		ObjectReference performRef = luter.GetLinkedRef(LinkPerformanceSpot)
		If luter.GetDistance(performRef) > 256.0
			If fMissedStartByHours > 0.6 && PlayerRef.GetDistance(luter) > SAFE_TELEPORT_DISTANCE && PlayerRef.GetDistance(performRef) > SAFE_TELEPORT_DISTANCE
				bTeleport = True
			Else
				bTravel = True
			EndIf
		EndIf
	Else ; iCampWaypoint == WAYPOINT_SANDBOX_INN
		If luter.GetParentCell() != InnCell
			If fMissedStartByHours > 0.6 && PlayerRef.GetDistance(luter) > SAFE_TELEPORT_DISTANCE && PlayerRef.GetParentCell() != InnCell
				bTeleport = True
			Else
				bTravel = True
			EndIf
		EndIf
	EndIf

	If bTravel
		If fNextUpdateTime > 0.5
			fNextUpdateTime = 0.5 ; Failsafe if the end of the travel package is not triggered for some reason
		EndIf
		SetNewWaypoint(iTravelWaypoint, False, fNextUpdateTime)
	Else
		SetNewWaypoint(iCampWaypoint, bTeleport, fNextUpdateTime)
	EndIf
EndFunction

Function SetNewWaypoint(Int iNewWaypoint, Bool bDoTeleport, Float fNextUpdateTime)
	If fNextUpdateTime >= 0.0
		RegisterForSingleUpdateGameTime(fNextUpdateTime + 0.025)
	EndIf

	If iNewWaypoint != CurrentWaypoint
		Actor luter  = Alias_LutePlayer.GetActorReference()
		Actor dancer = Alias_Dancer.GetActorReference()
		Bool bDoMusicReset = (CurrentWaypoint == WAYPOINT_PERFORM_MARKET)

		; Teleport destination for the lute player
		ObjectReference teleportLuterRef = None
		If bDoTeleport
			If iNewWaypoint == WAYPOINT_PERFORM_MARKET
				teleportLuterRef = luter.GetLinkedRef(LinkPerformanceSpot)
			ElseIf iNewWaypoint == WAYPOINT_SANDBOX_INN
				teleportLuterRef = luter.GetLinkedRef(LinkCustom01)
			EndIf
		EndIf

		; Teleport destination for the dancer
		ObjectReference teleportDancerRef = None
		If dancer
			If iNewWaypoint == WAYPOINT_PERFORM_MARKET
				ObjectReference performRef = dancer.GetLinkedRef(LinkPerformanceSpot)
				If bDoTeleport
					teleportDancerRef = performRef
				ElseIf dancer.GetDistance(performRef) > 256.0 && PlayerRef.GetDistance(dancer) > SAFE_TELEPORT_DISTANCE && PlayerRef.GetDistance(performRef) > SAFE_TELEPORT_DISTANCE
					teleportDancerRef = performRef
				EndIf
			ElseIf iNewWaypoint == WAYPOINT_SANDBOX_INN
				If bDoTeleport
					teleportDancerRef = teleportLuterRef
				ElseIf dancer.GetParentCell() != InnCell && PlayerRef.GetDistance(dancer) > SAFE_TELEPORT_DISTANCE && PlayerRef.GetParentCell() != InnCell
					teleportDancerRef = luter.GetLinkedRef(LinkCustom01) ; Yes, luter.GetLinkedRef
				EndIf
			EndIf
		EndIf

		; Wait for the lute player to finish his current song (if he's playing)
		(luter as _00E_BardPlayInstrumentScript).FadeAndStopMusic()

		CurrentWaypoint = iNewWaypoint

		If teleportLuterRef
			_00E_Func_SafeMove.SafeMoveTo(luter, teleportLuterRef)
		EndIf
		If teleportDancerRef
			_00E_Func_SafeMove.SafeMoveTo(dancer, teleportDancerRef)
		EndIf
		luter.EvaluatePackage()
		If dancer
			dancer.EvaluatePackage()
		EndIf

		If CurrentWaypoint == WAYPOINT_PERFORM_MARKET
			If IsTipBasketHidden
				IsTipBasketHidden = False
				TipBasket.MoveToMyEditorLocation()
			EndIf
		Else
			If IsTipBasketHidden == False
				IsTipBasketHidden = True
				TipBasket.MoveTo(TempStorageMarker)
			EndIf
		EndIf

		If bDoMusicReset
	 		; Failsafe remove silence if the lute player alias fails to do this
			ArkMarketGypsyPerformanceSilence.Remove()
		EndIf
	EndIf
EndFunction

Function GroupTravelFailsafe()
	If CurrentWaypoint == WAYPOINT_TRAVEL_INN || CurrentWaypoint == WAYPOINT_TRAVEL_MARKET
		Actor luter = Alias_LutePlayer.GetActorReference()
		Actor dancer = Alias_Dancer.GetActorReference()
		If luter && dancer && dancer.GetDistance(luter) >= 1024.0 && dancer.GetDistance(PlayerRef) > 4000.0
			_00E_Func_SafeMove.SafeMoveTo_NoWait(dancer, luter)
		EndIf
	EndIf
EndFunction