Scriptname _00E_PlayerhousingCurrentOManipulate extends ReferenceAlias

EffectShader Property _00E_PlayerHousingManipulationShader Auto
Sound Property UIMenuPrevNext Auto
Actor Property PlayerRef Auto

String Property CONTROL_TURN_LEFT  = "Strafe Left" AutoReadOnly
String Property CONTROL_TURN_RIGHT = "Strafe Right" AutoReadOnly
String Property CONTROL_MOVE_UP    = "Forward" AutoReadOnly
String Property CONTROL_MOVE_DOWN  = "Back" AutoReadOnly

Int Property TRANSLATION_TURN_LEFT  = 0 AutoReadOnly
Int Property TRANSLATION_TURN_RIGHT = 1 AutoReadOnly
Int Property TRANSLATION_MOVE_UP    = 2 AutoReadOnly
Int Property TRANSLATION_MOVE_DOWN  = 3 AutoReadOnly

Int Property STATE_IDLE     = 0 AutoReadOnly
Int Property STATE_UPDATING = 1 AutoReadOnly
Int Property STATE_WORKING  = 2 AutoReadOnly

Float Property ROTATION_SPEED_START = 15.0 AutoReadOnly
Float Property ROTATION_SPEED_FALLBACK = 20.0 AutoReadOnly
Float Property ROTATION_SPEED_MAX = 180.0 AutoReadOnly
Float Property ROTATION_SPEED_CHANGE_MOD = 0.25 AutoReadOnly
Float Property ROTATION_MAX = 90.0 AutoReadOnly

Float Property MOVEMENT_SPEED_START = 10.0 AutoReadOnly
Float Property MOVEMENT_SPEED_FALLBACK = 20.0 AutoReadOnly
Float Property MOVEMENT_SPEED_MAX = 200.0 AutoReadOnly
Float Property MOVEMENT_SPEED_CHANGE_MOD = 0.25 AutoReadOnly
Float Property MOVEMENT_MAX = 100.0 AutoReadOnly

Float Property FAST_TRANSLATION_SPEED = 1000000.0 AutoReadOnly

Float fObjectPosX
Float fObjectPosY
Float fObjectPosZ
Float fObjectAngleX
Float fObjectAngleY
Float fObjectAngleZ
Float fPlayerPosZ
Float fTablePosZ

Float fTranslationSpeed
Float fTranslationSpeedIncrease

Int iWorkingState
Int iCurrentTranslation
Bool bGamepadMode
Int iWatchedKeyCode
Int iTurnLeftKey
Int iTurnRightKey
Int iMoveUpKey
Int iMoveDownKey

Function Setup(ObjectReference curObjectRef)
	ForceRefTo(curObjectRef)

	iWorkingState = STATE_IDLE

	bGamepadMode = Game.UsingGamepad()
	If bGamepadMode == False
		iTurnLeftKey  = Input.GetMappedKey(CONTROL_TURN_LEFT, 0)
		iTurnRightKey = Input.GetMappedKey(CONTROL_TURN_RIGHT, 0)
		iMoveUpKey    = Input.GetMappedKey(CONTROL_MOVE_UP, 0)
		iMoveDownKey  = Input.GetMappedKey(CONTROL_MOVE_DOWN, 0)
	Else
		iTurnLeftKey  = 274
		iTurnRightKey = 275
		iMoveUpKey    = 280
		iMoveDownKey  = 281
	EndIf

	If PlayerRef == None ; Just in case
		PlayerRef = Game.GetPlayer()
	EndIf

	fPlayerPosZ = PlayerRef.GetPositionZ()
	fTablePosZ = fPlayerPosZ + 64.0

	GoToState("Working")

	TryRegisterForKey(iTurnLeftKey,  CONTROL_TURN_LEFT)
	TryRegisterForKey(iTurnRightKey, CONTROL_TURN_RIGHT)
	TryRegisterForKey(iMoveUpKey,    CONTROL_MOVE_UP)
	TryRegisterForKey(iMoveDownKey,  CONTROL_MOVE_DOWN)

	_00E_PlayerHousingManipulationShader.Play(curObjectRef)
EndFunction

Function TryRegisterForKey(Int iKeyCode, String control)
	If iKeyCode > 0
		RegisterForKey(iKeyCode)
	Else
		RegisterForControl(control)
	EndIf
EndFunction

Function Shutdown(Bool bRegisterNewOffsets)
	GoToState("")
	UnregisterForAllKeys()
	UnregisterForAllControls()
	UnregisterForUpdate()

	ObjectReference myRef = GetRef()
	If myRef != None
		_00E_PlayerHousingManipulationShader.Stop(myRef)

		While iWorkingState != STATE_IDLE && iWorkingState != STATE_WORKING
			Utility.WaitMenuMode(0.05)
		EndWhile
		If iWorkingState == STATE_WORKING
			myRef.StopTranslation()
		EndIf

		If bRegisterNewOffsets
			Float fNewOffsetAngleZ = myRef.GetAngleZ() - PlayerRef.GetAngleZ()
			Float fNewOffsetPosZ = myRef.GetPositionZ() - PlayerRef.GetPositionZ()
			(GetOwningQuest() as _00E_PlayerhousingMaster).RegisterNewHousingObjectOffsets(fNewOffsetAngleZ, fNewOffsetPosZ)
		EndIf

		Clear()
	EndIf
EndFunction

State Working
	Event OnKeyDown(Int iKeyCode)
		TryStartTranslation(KeyCodeToTranslationMode(iKeyCode), iKeyCode)
	EndEvent

	Event OnKeyUp(Int iKeyCode, Float fHoldTime)
		TryStopTranslation(KeyCodeToTranslationMode(iKeyCode))
	EndEvent

	Event OnControlDown(String control)
		TryStartTranslation(ControlToTranslationMode(control), 0)
	EndEvent

	Event OnControlUp(String control, Float fHoldTime)
		TryStopTranslation(ControlToTranslationMode(control))
	EndEvent

	Event OnUpdate()
		; OnControlUp is not always properly triggered. Maybe OnKeyUp too. The code below is a workaround for that.
		; For gamepads this failsafe is useless because Input.IsKeyPressed always returns False for controller buttons.
		If iWatchedKeyCode > 0 && iWorkingState == STATE_WORKING
			If Input.IsKeyPressed(iWatchedKeyCode)
				RegisterForSingleUpdate(0.05)
			Else
				TryStopTranslation(iCurrentTranslation)
			EndIf
		EndIf
	EndEvent

	Event OnTranslationComplete()
		; The previously started translation reached its end point

		If iWorkingState != STATE_WORKING
			Return
		EndIf
		iWorkingState = STATE_UPDATING

		ObjectReference myRef = GetRef()

		If iWatchedKeyCode <= 0 || fTranslationSpeedIncrease < 0.0
			; Do nothing
		ElseIf iCurrentTranslation == TRANSLATION_TURN_LEFT || iCurrentTranslation == TRANSLATION_TURN_RIGHT
			Float fRotationAngle = GetNextTranslationStep(ROTATION_SPEED_MAX, ROTATION_SPEED_CHANGE_MOD, ROTATION_MAX)
			fObjectAngleZ = myRef.GetAngleZ()
			TurnTranslate(myRef, fRotationAngle)

			iWorkingState = STATE_WORKING
			Return
		ElseIf iCurrentTranslation == TRANSLATION_MOVE_UP || iCurrentTranslation == TRANSLATION_MOVE_DOWN
			Float fMoveZ = GetNextTranslationStep(MOVEMENT_SPEED_MAX, MOVEMENT_SPEED_CHANGE_MOD, MOVEMENT_MAX)
			fObjectPosZ = myRef.GetPositionZ()
			MoveTranslate(myRef, fMoveZ)

			iWorkingState = STATE_WORKING
			Return
		EndIf

		; Fallback
		FinalizeTranslation(myRef)
		UIMenuPrevNext.Play(PlayerRef)

		iWorkingState = STATE_IDLE
	EndEvent
EndState

Int Function KeyCodeToTranslationMode(Int iKeyCode)
	If iKeyCode > 0
		If iKeyCode == iTurnLeftKey
			Return TRANSLATION_TURN_LEFT
		ElseIf iKeyCode == iTurnRightKey
			Return TRANSLATION_TURN_RIGHT
		ElseIf iKeyCode == iMoveUpKey
			Return TRANSLATION_MOVE_UP
		ElseIf iKeyCode == iMoveDownKey
			Return TRANSLATION_MOVE_DOWN
		EndIf
	EndIf

	Return -1
EndFunction

Int Function ControlToTranslationMode(String control)
	If control == CONTROL_TURN_LEFT
		Return TRANSLATION_TURN_LEFT
	ElseIf control == CONTROL_TURN_RIGHT
		Return TRANSLATION_TURN_RIGHT
	ElseIf control == CONTROL_MOVE_UP
		Return TRANSLATION_MOVE_UP
	ElseIf control == CONTROL_MOVE_DOWN
		Return TRANSLATION_MOVE_DOWN
	EndIf

	Return -1
EndFunction

Function TryStartTranslation(Int iTranslationMode, Int iTriggerKeyCode)
	If iTranslationMode < 0 || iWorkingState != STATE_IDLE
		Return
	EndIf

	iWorkingState = STATE_UPDATING
	iCurrentTranslation = iTranslationMode
	iWatchedKeyCode = iTriggerKeyCode

	ObjectReference myRef = GetRef()

	fObjectPosX = myRef.GetPositionX()
	fObjectPosY = myRef.GetPositionY()
	fObjectPosZ = myRef.GetPositionZ()

	fObjectAngleX = myRef.GetAngleX()
	fObjectAngleY = myRef.GetAngleY()
	fObjectAngleZ = myRef.GetAngleZ()

	fTranslationSpeedIncrease = 0.0

	If iCurrentTranslation == TRANSLATION_TURN_LEFT || iCurrentTranslation == TRANSLATION_TURN_RIGHT
		If iWatchedKeyCode > 0
			fTranslationSpeed = ROTATION_SPEED_START
			TurnTranslate(myRef, fTranslationSpeed * ROTATION_SPEED_CHANGE_MOD)
		Else
			fTranslationSpeed = ROTATION_SPEED_FALLBACK
			TurnTranslate(myRef, ROTATION_MAX)
		EndIf
	ElseIf iCurrentTranslation == TRANSLATION_MOVE_UP || iCurrentTranslation == TRANSLATION_MOVE_DOWN
		If iWatchedKeyCode > 0
			fTranslationSpeed = MOVEMENT_SPEED_START
			MoveTranslate(myRef, fTranslationSpeed * MOVEMENT_SPEED_CHANGE_MOD)
		Else
			fTranslationSpeed = MOVEMENT_SPEED_FALLBACK
			MoveTranslate(myRef, MOVEMENT_MAX)
		EndIf
	EndIf

	If iWatchedKeyCode > 0 && bGamepadMode == False
		RegisterForSingleUpdate(0.05)
	EndIf

	iWorkingState = STATE_WORKING
EndFunction

Function TryStopTranslation(Int iTranslationMode)
	If iCurrentTranslation != iTranslationMode || iWorkingState != STATE_WORKING
		Return
	EndIf

	iWorkingState = STATE_UPDATING

	ObjectReference myRef = GetRef()
	myRef.StopTranslation()
	UnregisterForUpdate()
	FinalizeTranslation(myRef)

	iWorkingState = STATE_IDLE
EndFunction

Function TurnTranslate(ObjectReference myRef, Float fRotationAngle)
	If iCurrentTranslation == TRANSLATION_TURN_LEFT
		fObjectAngleZ -= fRotationAngle
	Else
		fObjectAngleZ += fRotationAngle
	EndIf
	myRef.TranslateTo(fObjectPosX, fObjectPosY, fObjectPosZ, fObjectAngleX, fObjectAngleY, fObjectAngleZ, FAST_TRANSLATION_SPEED, fTranslationSpeed)
EndFunction

Function MoveTranslate(ObjectReference myRef, Float fMoveZ)
	If iCurrentTranslation == TRANSLATION_MOVE_DOWN
		Float fNewZ = fObjectPosZ - fMoveZ
		If fObjectPosZ > fTablePosZ && fNewZ < fTablePosZ
			; A primitive "drop on the table"
			fNewZ = fTablePosZ
			fTranslationSpeedIncrease = -1.0 ; Stop on reaching fPlayerPosZ.
		ElseIf fObjectPosZ > fPlayerPosZ && fNewZ < fPlayerPosZ
			; A primitive "drop on the floor"
			fNewZ = fPlayerPosZ
			fTranslationSpeedIncrease = -1.0 ; Stop on reaching fPlayerPosZ.
		EndIf
		fObjectPosZ = fNewZ
	Else
		fObjectPosZ += fMoveZ
	EndIf
	myRef.TranslateTo(fObjectPosX, fObjectPosY, fObjectPosZ, fObjectAngleX, fObjectAngleY, fObjectAngleZ, fTranslationSpeed, FAST_TRANSLATION_SPEED)
EndFunction

Float Function GetNextTranslationStep(Float fSpeedMax, Float fSpeedChangeMod, Float fChangeMax)
	If fTranslationSpeed < fSpeedMax
		If fTranslationSpeedIncrease < 20.0
			fTranslationSpeedIncrease += 5.0
		EndIf
		fTranslationSpeed += fTranslationSpeedIncrease
		If fTranslationSpeed > fSpeedMax
			fTranslationSpeed = fSpeedMax
		EndIf
	EndIf
	Float fResult = fTranslationSpeed * fSpeedChangeMod
	If fTranslationSpeed >= fSpeedMax || fResult > fChangeMax
		Return fChangeMax
	Else
		Return fResult
	EndIf
EndFunction	

Function FinalizeTranslation(ObjectReference myRef)
	; TranslateTo below prevents the object from twitching and becoming blurry when a translation ends
	If iCurrentTranslation == TRANSLATION_TURN_LEFT || iCurrentTranslation == TRANSLATION_TURN_RIGHT
		fObjectAngleZ = myRef.GetAngleZ()
	ElseIf iCurrentTranslation == TRANSLATION_MOVE_UP || iCurrentTranslation == TRANSLATION_MOVE_DOWN
		fObjectPosZ = myRef.GetPositionZ()
	EndIf
	myRef.TranslateTo(fObjectPosX, fObjectPosY, fObjectPosZ, fObjectAngleX, fObjectAngleY, fObjectAngleZ, FAST_TRANSLATION_SPEED, FAST_TRANSLATION_SPEED)
EndFunction