/**
 * Component: HomeBackground@2
 * Background of the home menu with the car on the podium and animated background
 * Only one instance of this component per manialink page
 */
#Const Version "2.3.2"
#Const ScriptName "Libs/Nadeo/Trackmania/Components/HomeBackground@2.Script.txt"

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Libraries
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "AnimLib" as AL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CMGame/Menus/ShareMenuNavigation_ML.Script.txt" as ShareMenuNavigation
#Include "Libs/Nadeo/CMGame/Utils/Context.Script.txt" as Context
#Include "Libs/Nadeo/CMGame/Utils/ManiaView2.Script.txt" as MV
#Include "Libs/Nadeo/CMGame/Utils/MenuSceneManager.Script.txt" as MenuSceneManager
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MainMenuConst
#Include "Libs/Nadeo/Trackmania/Stores/CampaignStore_ML.Script.txt" as CampaignStore

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Structures
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Struct K_Private_Controls {
	CMlFrame Frame_HomeBackground;
	CMlQuad Quad_BackgroundVideoTransition;
	CMlQuad Quad_BackgroundVideoActive;
	CMlQuad Quad_ForegroundImageTransition;
	CMlQuad Quad_ForegroundImageActive;
	CMlCamera Camera_Scene;
}
#Struct K_Private_CameraScene {
	Integer ReloadCounter;
	Ident SceneId;
	Ident PodiumItemId;
	Ident DefaultCarItemId;
	Text SpecialCarModelName;
	Ident SpecialCarItemId;
	Ident CurrentCarItemId;
	Boolean IsSpecialCarDisplayed;
	Ident PilotItemId;
}
#Struct K_Private_Anim {
	Integer StartTime;
	Integer EndTime;
	Integer Duration;
	Real CurrentValue;
	Real StartValue;
	Real TargetValue;
	Real DeltaValue;
}
#Struct K_Private_Component {
	K_Private_Controls Controls;
	K_Private_CameraScene CameraScene;
	Boolean IsEnabled;
	Boolean IsInputEnabled;
	Text BackgroundVideoActiveUrl;
	Ident BackgroundVideoActiveId;
	Ident BackgroundVideoTransitionId;
	Integer BackgroundVideoTransitionEndTime;
	Integer BackgroundVideoTransitionState;
	Text ForegroundImageActiveUrl;
	Integer ForegroundImageTransitionEndTime;
	Integer ForegroundImageTransitionState;
	Integer MenuMediaUpdate;
	Integer PilotEmoteCooldownEndTime;
	Boolean IsWaitingCarStateUpdate;
	Integer CarSteerDirection;
	K_Private_Anim CarSteerAnim;
	Integer CarBoost;
	K_Private_Anim CarBoostAnim;
	Integer CarBoostHoverCooldownEndTime;
	Integer CarBoostTransformCooldownEndTime;
	Vec3 CarPosition;
	Integer UpdateCameraSceneCubeMapTime;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_Name "trackmania-home-background-2" ///< Component name
#Const P "Trackmania_HomeBackground2::" ///< Prefix used to differentiate functions/variables in the script
#Const Q "Trackmania_HomeBackground2"

#Const C_Private_DefaultBackgroundVideo "file://Media/Manialinks/Nadeo/Trackmania/Menus/HomeBackground2/MenuBackground.webm"
#Const C_Private_DefaultForegroundImage "file://Media/Manialinks/Nadeo/Trackmania/Menus/HomeBackground2/MenuForeground.dds"

#Const C_Private_BackgroundVideoTransitionDuration 5000
#Const C_Private_ForegroundImageTransitionDuration 5000

#Const C_Private_TransitionState_Complete 0
#Const C_Private_TransitionState_Download 1
#Const C_Private_TransitionState_Play 2

#Const C_Private_SceneLayout "Empty" ///< Check ManiaScript documentation to see all layouts
#Const C_Private_ScenePostFx 0.15
#Const C_Private_CameraPosition <1.021, 1.126, -6.816>
#Const C_Private_CameraRotation 0.35
#Const C_Private_CameraFOV 35.5

#Const C_Private_PodiumModelName "/Stadium/Items/PodiumDisk.Item.Gbx" //< The leading slash is required
#Const C_Private_PodiumSkinName ""
#Const C_Private_PodiumSkinUrl ""
#Const C_Private_PodiumPosition <0., 0., 0.>
#Const C_Private_PodiumRotation 0.
#Const C_Private_PodiumBaseColor 0.4
#Const C_Private_PodiumCubeReflect 0.3
#Const C_Private_PodiumPlaneReflect 0.5

#Const MainMenuConst::C_CameraSkinModel as C_Private_SportCarModelName
#Const MainMenuConst::C_CameraSkinModelSnow as C_Private_SnowCarModelName
#Const MainMenuConst::C_CameraSkinModelRally as C_Private_RallyCarModelName
#Const MainMenuConst::C_CameraSkinModelDesert as C_Private_DesertCarModelName
#Const C_Private_SportCarSkinName ""
#Const C_Private_SportCarSkinUrl ""
#Const C_Private_CarPosition_SportCar <-0.3, 0.235, 0.2>
#Const C_Private_CarPosition_RallyCar <-0.3, 0.270, 0.2>
#Const C_Private_CarPosition_SnowCar <-0.3, 0.420, 0.2>
#Const C_Private_CarPosition_DesertCar <-0.3, 0.245, 0.2>
#Const C_Private_CarRotation 147.
#Const C_Private_CarIsTurntable False
#Const C_Private_CarCanTransform True
#Const C_Private_CarSteerAnimDuration 500
#Const C_Private_CarBrakeLightEnabled False
#Const C_Private_CarFrontLightEnabled False
#Const C_Private_CarBurnoutSmokeEnabled False
#Const C_Private_CarTurboLevel 0

#Const C_Private_CarSteerDirection_Left -1
#Const C_Private_CarSteerDirection_Neutral 0
#Const C_Private_CarSteerDirection_Right 1
#Const C_Private_CarSteerAngleList [
	-1 => -0.7,
	0 => 0.,
	1 => 0.7
]

#Const C_Private_CarBoost_Off 0
#Const C_Private_CarBoost_HoverUp 1
#Const C_Private_CarBoost_HoverDown 2
#Const C_Private_CarBoost_HoverLoop 3
#Const C_Private_CarBoost_TransformUp 4
#Const C_Private_CarBoost_TransformDown 5

#Const C_Private_CarBoostLevel_Off 0
#Const C_Private_CarBoostLevel_Down 1
#Const C_Private_CarBoostLevel_Up 2

#Const C_Private_CarBoostOffPosY 0.
#Const C_Private_CarBoostHoverPosY 0.21
#Const C_Private_CarBoostHoverAnimDuration 1000
#Const C_Private_CarBoostHoverAnimFrequency 0.002
#Const C_Private_CarBoostHoverAnimAmplitude 0.02
#Const C_Private_CarBoostHoverCooldownDuration 3000
#Const C_Private_CarBoostTransformPosY 3.7
#Const C_Private_CarBoostTransformAnimDuration 1500
#Const C_Private_CarBoostTransformCooldownDuration 15000
#Const C_Private_CarBoostTransformProbability 5. //< Percentage between 0. and 100.

#Const MainMenuConst::C_CameraPilotModel as C_Private_PilotModelName
#Const C_Private_PilotSkinName ""
#Const C_Private_PilotSkinUrl ""
#Const C_Private_PilotPosition <-0.78, 0.240, -1.7>
#Const C_Private_PilotRotation 140.
#Const C_Private_PilotIsTurntable False
#Const C_Private_PilotEmoteCooldownDuration 10000

// AltitudeDeg: 0=horizon, 90=Zenith
// AzimutDeg: 0=North(+Z), 90=East(-X), 180=South(-Z), 270=West(+X)
#Const C_Private_LightRGB <1., 0.85, 0.83>
#Const C_Private_LightIntensity 4.0
#Const C_Private_LightAltitudeDeg 60.0
#Const C_Private_LightAzimuteDeg 182.0

#Const C_Private_Class_ComponentContainer "trackmania-home-background-2-class-component-container"
#Const C_Private_Class_Fullscreen "trackmania-home-background-2-class-fullscreen"
#Const C_Private_Id_Quad_BackgroundVideoTransition "trackmania-home-background-2-quad-background-transition"
#Const C_Private_Id_Quad_BackgroundVideoActive "trackmania-home-background-2-quad-background-active"
#Const C_Private_Id_Quad_ForegroundImageTransition "trackmania-home-background-2-quad-foreground-transition"
#Const C_Private_Id_Quad_ForegroundImageActive "trackmania-home-background-2-quad-foreground-active"
#Const C_Private_Id_Camera_Scene "trackmania-home-background-2-camera-scene"

#Const C_Private_Anim_Null K_Private_Anim {
	StartTime = -1,
	EndTime = -1,
	Duration = 0,
	CurrentValue = 0.,
	StartValue = 0.,
	TargetValue = 0.,
	DeltaValue = 0.
}
#Const C_Private_Component_Null K_Private_Component {
	Controls = K_Private_Controls {},
	CameraScene = K_Private_CameraScene {
		ReloadCounter = 0,
		SceneId = NullId,
		PodiumItemId = NullId,
		DefaultCarItemId = NullId,
		SpecialCarModelName = "",
		SpecialCarItemId = NullId,
		CurrentCarItemId = NullId,
		IsSpecialCarDisplayed = False,
		PilotItemId = NullId
	},
	IsEnabled = False,
	IsInputEnabled = False,
	BackgroundVideoActiveUrl = "",
	BackgroundVideoActiveId = NullId,
	BackgroundVideoTransitionId = NullId,
	BackgroundVideoTransitionEndTime = -1,
	BackgroundVideoTransitionState = 0,
	ForegroundImageActiveUrl = "",
	ForegroundImageTransitionEndTime = -1,
	ForegroundImageTransitionState = 0,
	MenuMediaUpdate = 0,
	PilotEmoteCooldownEndTime = 0,
	IsWaitingCarStateUpdate = True,
	CarSteerDirection = 0,
	CarSteerAnim = K_Private_Anim {},
	CarBoost = 0,
	CarBoostAnim = K_Private_Anim {},
	CarBoostHoverCooldownEndTime = 0,
	CarBoostTransformCooldownEndTime = 0,
	CarPosition = <0., 0., 0.>,
	UpdateCameraSceneCubeMapTime = -1
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Private functions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Get the component manialink
Text Private_GetManialink() {
	return
"""
<stylesheet>
	<style class="{{{C_Private_Class_Fullscreen}}}" size="320 180" halign="center" valign="center" />
</stylesheet>
<frame class="{{{C_Private_Class_ComponentContainer}}}">
	<quad class="{{{C_Private_Class_Fullscreen}}}" z-index="1" bgcolor="000000" fullscreen="1" />
	<frame z-index="2">
		<frame z-index="1">
			<quad id="{{{C_Private_Id_Quad_BackgroundVideoTransition}}}" class="{{{C_Private_Class_Fullscreen}}}" z-index="1" />
			<quad id="{{{C_Private_Id_Quad_BackgroundVideoActive}}}" class="{{{C_Private_Class_Fullscreen}}}" z-index="2" />
		</frame>
		<frame z-index="2">
			<quad id="{{{C_Private_Id_Quad_ForegroundImageTransition}}}" class="{{{C_Private_Class_Fullscreen}}}" z-index="1" />
			<quad id="{{{C_Private_Id_Quad_ForegroundImageActive}}}" class="{{{C_Private_Class_Fullscreen}}}" z-index="2" />
		</frame>
		<!-- Cannot use `C_Private_Class_Fullscreen` class, properties of the camera must be set directly on the node -->
		<camera id="{{{C_Private_Id_Camera_Scene}}}" z-index="3" size="320 180" halign="center" valign="center" fullscreen="1" />
	</frame>
	<!-- This dark gradient at the bottom of the screen allows the game version text to be displayed over bright backgrounds -->
	<quad pos="0 -91" size="320 20" halign="center" valign="bottom" style="Bgs1" substyle="BgGradBottom" opacity="0.5" z-index="3"/>
	<!-- Special `id` for C++ that will create a blurred background for non 16:9 aspect ratios -->
	<quad id="maniaplanet.BlurContentsDivider" z-index="4" hidden="1" />
</frame>
""";
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Delete the transition background video when we don't need it anymore
K_Private_Component Private_DeleteBackgroundVideoTransition(CMlScript _Context, K_Private_Component _Component) {
	if (
		_Component.BackgroundVideoTransitionState != C_Private_TransitionState_Complete ||
		_Component.BackgroundVideoTransitionId == NullId
	) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;

	if (_Context.Video.Videos.existskey(Component.BackgroundVideoTransitionId)) {
		_Context.Video.DestroyVideo(_Context.Video.Videos[Component.BackgroundVideoTransitionId]);
	}
	Component.BackgroundVideoTransitionId = NullId;
	Component.Controls.Quad_BackgroundVideoTransition.Image = Null;

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Play/Pause the background video
Void Private_SetBackgroundVideoIsPlaying(CMlScript _Context, K_Private_Component _Component, Boolean _IsPlaying) {
	if (_Component.BackgroundVideoActiveId != NullId && _Context.Video.Videos.existskey(_Component.BackgroundVideoActiveId)) {
		if (_IsPlaying && _Component.Controls.Quad_BackgroundVideoActive.Visible) {
			_Context.Video.Videos[_Component.BackgroundVideoActiveId].Play();
		} else {
			_Context.Video.Videos[_Component.BackgroundVideoActiveId].Pause();
		}
	}
	if (_Component.BackgroundVideoTransitionId != NullId && _Context.Video.Videos.existskey(_Component.BackgroundVideoTransitionId)) {
		if (_IsPlaying && _Component.Controls.Quad_BackgroundVideoTransition.Visible) {
			_Context.Video.Videos[_Component.BackgroundVideoTransitionId].Play();
		} else {
			_Context.Video.Videos[_Component.BackgroundVideoTransitionId].Pause();
		}
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Check if the camera scene is valid
Boolean Private_IsCameraSceneValid(CMlScript _Context, K_Private_Component _Component) {
	return (
		_Component.CameraScene.SceneId != NullId &&
		!MenuSceneManager::PageNeedToRecreateScene(_Context.Page, _Context.LocalUser)
	);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Select which video to use for the camera scene cube map
Void Private_UpdateCameraSceneCubeMap(CMlScript _Context, K_Private_Component _Component) {
	if (!Private_IsCameraSceneValid(_Context, _Component)) return;

	if (
		_Component.BackgroundVideoTransitionState == C_Private_TransitionState_Download &&
		_Component.BackgroundVideoTransitionId != NullId &&
		_Context.Video.Videos.existskey(_Component.BackgroundVideoTransitionId)
	) {
		_Context.MenuSceneMgr.CubeMapSetFromEquirect(
			_Component.CameraScene.SceneId,
			_Component.Controls.Quad_BackgroundVideoTransition
		);
	} else if (
		_Component.BackgroundVideoActiveId != NullId &&
		_Context.Video.Videos.existskey(_Component.BackgroundVideoActiveId)
	) {
		_Context.MenuSceneMgr.CubeMapSetFromEquirect(
			_Component.CameraScene.SceneId,
			_Component.Controls.Quad_BackgroundVideoActive
		);
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Plan a cube map update for the next frame
K_Private_Component Private_UpdateCameraSceneCubeMapNextFrame(K_Private_Component _Component) {
	declare K_Private_Component Component = _Component;
	Component.UpdateCameraSceneCubeMapTime = Now;
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the state of background video transition
K_Private_Component Private_SetBackgroundVideoTransitionState(
	CMlScript _Context,
	K_Private_Component _Component,
	Integer _BackgroundVideoTransitionState,
	Boolean _IsInit
) {
	if (!_IsInit && _Component.BackgroundVideoTransitionState == _BackgroundVideoTransitionState) return _Component;

	declare K_Private_Component Component = _Component;

	Component.BackgroundVideoTransitionState = _BackgroundVideoTransitionState;

	_Context.AnimMgr.Flush(Component.Controls.Quad_BackgroundVideoActive);
	switch (Component.BackgroundVideoTransitionState) {
		case C_Private_TransitionState_Download: {
			Component.Controls.Quad_BackgroundVideoTransition.Visible = True;
			Component.Controls.Quad_BackgroundVideoActive.Visible = True;
			Component.Controls.Quad_BackgroundVideoActive.Opacity = 0.;
			Component.BackgroundVideoTransitionEndTime = -1;
		}
		case C_Private_TransitionState_Play: {
			Component.Controls.Quad_BackgroundVideoTransition.Visible = True;
			Component.Controls.Quad_BackgroundVideoActive.Visible = True;
			Component.Controls.Quad_BackgroundVideoActive.Opacity = 0.;
			_Context.AnimMgr.Add(Component.Controls.Quad_BackgroundVideoActive, """<anim opacity="1" />""", C_Private_BackgroundVideoTransitionDuration, CAnimManager::EAnimManagerEasing::Linear);
			Component.BackgroundVideoTransitionEndTime = Now + C_Private_BackgroundVideoTransitionDuration;
		}
		case C_Private_TransitionState_Complete: {
			Component.Controls.Quad_BackgroundVideoTransition.Visible = False;
			Component.Controls.Quad_BackgroundVideoActive.Visible = True;
			Component.Controls.Quad_BackgroundVideoActive.Opacity = 1.;
			Component.BackgroundVideoTransitionEndTime = -1;
		}
	}

	Component = Private_DeleteBackgroundVideoTransition(_Context, Component);
	Component = Private_UpdateCameraSceneCubeMapNextFrame(Component);
	Private_SetBackgroundVideoIsPlaying(_Context, Component, Component.IsEnabled);

	return Component;
}
K_Private_Component Private_SetBackgroundVideoTransitionState(CMlScript _Context, K_Private_Component _Component, Integer _BackgroundVideoState) {
	return Private_SetBackgroundVideoTransitionState(_Context, _Component, _BackgroundVideoState, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Update the state of background video transition
K_Private_Component Private_UpdateBackgroundVideoTransitionState(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.BackgroundVideoTransitionState == C_Private_TransitionState_Complete) return _Component;

	declare K_Private_Component Component = _Component;

	switch (Component.BackgroundVideoTransitionState) {
		case C_Private_TransitionState_Download: {
			if (
				Component.BackgroundVideoActiveId == NullId ||
				!_Context.Video.Videos.existskey(Component.BackgroundVideoActiveId) ||
				!_Context.Video.Videos[Component.BackgroundVideoActiveId].DownloadInProgress
			) {
				Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Play);
			}
		}
		case C_Private_TransitionState_Play: {
			if (
				Component.BackgroundVideoTransitionEndTime < 0 ||
				Component.BackgroundVideoTransitionEndTime <= Now
			) {
				Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Complete);
			}
		}
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the video displayed in the background
K_Private_Component Private_SetBackgroundVideo(CMlScript _Context, K_Private_Component _Component, Text _BackgroundVideoActiveUrl, Boolean _IsInit) {
	if (!_IsInit && _Component.BackgroundVideoActiveUrl == _BackgroundVideoActiveUrl) return _Component;

	declare K_Private_Component Component = _Component;

	// Destroy previous transition video
	if (Component.BackgroundVideoTransitionId != NullId && _Context.Video.Videos.existskey(Component.BackgroundVideoTransitionId)) {
		_Context.Video.DestroyVideo(_Context.Video.Videos[Component.BackgroundVideoTransitionId]);
	}
	// Move active video to transition video
	Component.BackgroundVideoTransitionId = Component.BackgroundVideoActiveId;
	if (Component.BackgroundVideoTransitionId != NullId && _Context.Video.Videos.existskey(Component.BackgroundVideoTransitionId)) {
		Component.Controls.Quad_BackgroundVideoTransition.Image = _Context.Video.Videos[Component.BackgroundVideoTransitionId].Image;
		Component.Controls.Quad_BackgroundVideoTransition.Visible = True;
	} else {
		Component.Controls.Quad_BackgroundVideoTransition.Image = Null;
		Component.Controls.Quad_BackgroundVideoTransition.Visible = False;
	}

	// Create new active video
	Component.BackgroundVideoActiveUrl = _BackgroundVideoActiveUrl;
	declare CVideo ActiveVideo;
	if (Component.BackgroundVideoActiveUrl != "" && _Context.Http.IsValidUrl(Component.BackgroundVideoActiveUrl)) {
		ActiveVideo <=> _Context.Video.CreateVideo(Component.BackgroundVideoActiveUrl, True, True);
	}
	if (ActiveVideo == Null) {
		Component.BackgroundVideoActiveId = NullId;
		Component.Controls.Quad_BackgroundVideoActive.Image = Null;
	} else {
		Component.BackgroundVideoActiveId = ActiveVideo.Id;
		Component.Controls.Quad_BackgroundVideoActive.Image = ActiveVideo.Image;
	}

	declare Boolean Trackmania_ComponentsHomeBackground2_SkipBackgroundVideoTransition for _Context.System = False;
	if (_IsInit) {
		Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Complete);
	} else if (
		Component.BackgroundVideoActiveId != NullId &&
		_Context.Video.Videos.existskey(Component.BackgroundVideoActiveId) &&
		_Context.Video.Videos[Component.BackgroundVideoActiveId].DownloadInProgress
	) {
		Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Download);
	} else if (Trackmania_ComponentsHomeBackground2_SkipBackgroundVideoTransition) {
		Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Complete);
	} else {
		Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Play);
	}
	Trackmania_ComponentsHomeBackground2_SkipBackgroundVideoTransition = False;

	Component = Private_DeleteBackgroundVideoTransition(_Context, Component);
	Private_SetBackgroundVideoIsPlaying(_Context, Component, Component.IsEnabled);

	return Private_UpdateBackgroundVideoTransitionState(_Context, Component);
}
K_Private_Component Private_SetBackgroundVideo(CMlScript _Context, K_Private_Component _Component, Text _BackgroundVideoActiveUrl) {
	return Private_SetBackgroundVideo(_Context, _Component, _BackgroundVideoActiveUrl, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Delete the transition foreground image when we don't need it anymore
K_Private_Component Private_DeleteForegroundImageTransition(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.ForegroundImageTransitionState != C_Private_TransitionState_Complete) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;
	Component.Controls.Quad_ForegroundImageTransition.ImageUrl = "";
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the state of foreground image transition
K_Private_Component Private_SetForegroundImageTransitionState(
	CMlScript _Context,
	K_Private_Component _Component,
	Integer _ForegroundImageTransitionState,
	Boolean _IsInit
) {
	if (!_IsInit && _Component.ForegroundImageTransitionState == _ForegroundImageTransitionState) return _Component;

	declare K_Private_Component Component = _Component;

	Component.ForegroundImageTransitionState = _ForegroundImageTransitionState;

	_Context.AnimMgr.Flush(Component.Controls.Quad_ForegroundImageActive);
	switch (Component.ForegroundImageTransitionState) {
		case C_Private_TransitionState_Download: {
			Component.Controls.Quad_ForegroundImageTransition.Visible = True;
			Component.Controls.Quad_ForegroundImageActive.Visible = True;
			Component.Controls.Quad_ForegroundImageActive.Opacity = 0.;
			Component.ForegroundImageTransitionEndTime = -1;
		}
		case C_Private_TransitionState_Play: {
			Component.Controls.Quad_ForegroundImageTransition.Visible = True;
			Component.Controls.Quad_ForegroundImageActive.Visible = True;
			Component.Controls.Quad_ForegroundImageActive.Opacity = 0.;
			_Context.AnimMgr.Add(Component.Controls.Quad_ForegroundImageActive, """<anim opacity="1" />""", C_Private_ForegroundImageTransitionDuration / 2, CAnimManager::EAnimManagerEasing::Linear);
			_Context.AnimMgr.Add(Component.Controls.Quad_ForegroundImageTransition, """<anim opacity="0" />""", Now + (C_Private_ForegroundImageTransitionDuration / 2), C_Private_ForegroundImageTransitionDuration / 2, CAnimManager::EAnimManagerEasing::Linear);
			Component.ForegroundImageTransitionEndTime = Now + C_Private_ForegroundImageTransitionDuration;
		}
		case C_Private_TransitionState_Complete: {
			Component.Controls.Quad_ForegroundImageTransition.Visible = False;
			Component.Controls.Quad_ForegroundImageActive.Visible = True;
			Component.Controls.Quad_ForegroundImageActive.Opacity = 1.;
			Component.ForegroundImageTransitionEndTime = -1;
		}
	}

	Component = Private_DeleteForegroundImageTransition(_Context, Component);

	return Component;
}
K_Private_Component Private_SetForegroundImageTransitionState(CMlScript _Context, K_Private_Component _Component, Integer _ForegroundImageState) {
	return Private_SetForegroundImageTransitionState(_Context, _Component, _ForegroundImageState, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Update the state of foreground image transition
K_Private_Component Private_UpdateForegroundImageTransitionState(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.ForegroundImageTransitionState == C_Private_TransitionState_Complete) return _Component;

	declare K_Private_Component Component = _Component;

	switch (Component.ForegroundImageTransitionState) {
		case C_Private_TransitionState_Download: {
			if (!_Component.Controls.Quad_ForegroundImageActive.DownloadInProgress) {
				Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Play);
			}
		}
		case C_Private_TransitionState_Play: {
			if (
				Component.ForegroundImageTransitionEndTime < 0 ||
				Component.ForegroundImageTransitionEndTime <= Now
			) {
				Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Complete);
			}
		}
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the image displayed in the foreground
K_Private_Component Private_SetForegroundImage(CMlScript _Context, K_Private_Component _Component, Text _ForegroundImageActiveUrl, Boolean _IsInit) {
	if (!_IsInit && _Component.ForegroundImageActiveUrl == _ForegroundImageActiveUrl) return _Component;

	declare K_Private_Component Component = _Component;

	// Move active image to transition image
	Component.Controls.Quad_ForegroundImageTransition.ImageUrl = Component.Controls.Quad_ForegroundImageActive.ImageUrl;
	Component.Controls.Quad_ForegroundImageTransition.Visible = (Component.Controls.Quad_ForegroundImageTransition.ImageUrl != "");

	// Set new active image
	Component.ForegroundImageActiveUrl = _ForegroundImageActiveUrl;
	Component.Controls.Quad_ForegroundImageActive.ImageUrl = Component.ForegroundImageActiveUrl;

	declare Boolean Trackmania_ComponentsHomeBackground2_SkipForegroundImageTransition for _Context.System = False;
	if (_IsInit) {
		Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Complete);
	} else if (Component.Controls.Quad_ForegroundImageActive.DownloadInProgress) {
		Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Download);
	} else if (Trackmania_ComponentsHomeBackground2_SkipForegroundImageTransition) {
		Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Complete);
	} else {
		Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Play);
	}
	Trackmania_ComponentsHomeBackground2_SkipForegroundImageTransition = False;

	Component = Private_DeleteForegroundImageTransition(_Context, Component);

	return Private_UpdateForegroundImageTransitionState(_Context, Component);
}
K_Private_Component Private_SetForegroundImage(CMlScript _Context, K_Private_Component _Component, Text _ForegroundImageActiveUrl) {
	return Private_SetForegroundImage(_Context, _Component, _ForegroundImageActiveUrl, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Select if the default or special car is displayed
K_Private_Component Private_SetIsSpecialCarDisplayed(CMlScript _Context, K_Private_Component _Component, Boolean _IsSpecialCarDisplayed) {
	if (!Private_IsCameraSceneValid(_Context, _Component)) return _Component;

	declare K_Private_Component Component = _Component;

	Component.CameraScene.IsSpecialCarDisplayed = _IsSpecialCarDisplayed;

	if (Component.CameraScene.IsSpecialCarDisplayed && Component.CameraScene.SpecialCarItemId != NullId) {
		Component.CameraScene.CurrentCarItemId = Component.CameraScene.SpecialCarItemId;
		switch (Component.CameraScene.SpecialCarModelName) {
			case C_Private_RallyCarModelName: Component.CarPosition = C_Private_CarPosition_RallyCar;
			case C_Private_SnowCarModelName: Component.CarPosition = C_Private_CarPosition_SnowCar;
			case C_Private_DesertCarModelName: Component.CarPosition = C_Private_CarPosition_DesertCar;
			default: Component.CarPosition = C_Private_CarPosition_SportCar;
		}
	} else {
		Component.CameraScene.CurrentCarItemId = Component.CameraScene.DefaultCarItemId;
		Component.CarPosition = C_Private_CarPosition_SportCar;
	}

	if (Component.CameraScene.DefaultCarItemId != NullId) {
		_Context.MenuSceneMgr.ItemSetVisible(
			Component.CameraScene.SceneId,
			Component.CameraScene.DefaultCarItemId,
			Component.CameraScene.DefaultCarItemId == Component.CameraScene.CurrentCarItemId
		);
	}
	if (Component.CameraScene.SpecialCarItemId != NullId) {
		_Context.MenuSceneMgr.ItemSetVisible(
			Component.CameraScene.SceneId,
			Component.CameraScene.SpecialCarItemId,
			Component.CameraScene.SpecialCarItemId == Component.CameraScene.CurrentCarItemId
		);
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Update the visibility of the camera control that display the scene
Void Private_UpdateCameraSceneVisibility(K_Private_Component _Component) {
	_Component.Controls.Camera_Scene.Visible = (_Component.CameraScene.SceneId != NullId && _Component.IsEnabled);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Destroy the pilot for the camera scene
K_Private_Component Private_DestroyCameraScene_Pilot(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.CameraScene.SceneId == NullId || _Component.CameraScene.PilotItemId == NullId) return _Component;

	declare K_Private_Component Component = _Component;
	_Context.MenuSceneMgr.ItemDestroy(Component.CameraScene.SceneId, Component.CameraScene.PilotItemId);
	Component.CameraScene.PilotItemId = NullId;
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Create the pilot for the camera scene
K_Private_Component Private_CreateCameraScene_Pilot(CMlScript _Context, K_Private_Component _Component, Boolean _IsInit) {
	if (!Private_IsCameraSceneValid(_Context, _Component)) return _Component;
	if (!_IsInit && _Component.CameraScene.PilotItemId != NullId) return _Component;

	declare K_Private_Component Component = Private_DestroyCameraScene_Pilot(_Context, _Component);

	if (Component.CameraScene.PilotItemId == NullId) {
		if (_Context.LocalUser == Null) {
			Component.CameraScene.PilotItemId = _Context.MenuSceneMgr.ItemCreate(
				Component.CameraScene.SceneId,
				C_Private_PilotModelName,
				C_Private_PilotSkinName,
				C_Private_PilotSkinUrl
			);
		} else {
			Component.CameraScene.PilotItemId = _Context.MenuSceneMgr.ItemCreate(
				Component.CameraScene.SceneId,
				C_Private_PilotModelName,
				_Context.LocalUser.Model_CharacterPilot_SkinName,
				_Context.LocalUser.Model_CharacterPilot_SkinUrl,
				_Context.LocalUser.Character_SkinOptions
			);
		}
	}

	if (Component.CameraScene.PilotItemId != NullId) {
		if (_Context.LocalUser != Null) {
			_Context.MenuSceneMgr.ItemSetPlayerState(
				Component.CameraScene.SceneId,
				Component.CameraScene.PilotItemId,
				_Context.LocalUser
			);
		}
		_Context.MenuSceneMgr.ItemSetLocation(
			Component.CameraScene.SceneId,
			Component.CameraScene.PilotItemId,
			C_Private_PilotPosition,
			C_Private_PilotRotation,
			C_Private_PilotIsTurntable
		);
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Destroy the car for the camera scene
K_Private_Component Private_DestroyCameraScene_Car(CMlScript _Context, K_Private_Component _Component) {
	if (
		_Component.CameraScene.SceneId == NullId || (
			_Component.CameraScene.DefaultCarItemId == NullId &&
			_Component.CameraScene.SpecialCarItemId == NullId &&
			_Component.CameraScene.CurrentCarItemId == NullId
		)
	) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;
	if (Component.CameraScene.DefaultCarItemId != NullId) {
		_Context.MenuSceneMgr.ItemDestroy(Component.CameraScene.SceneId, Component.CameraScene.DefaultCarItemId);
		Component.CameraScene.DefaultCarItemId = NullId;
	}
	if (Component.CameraScene.SpecialCarItemId != NullId) {
		_Context.MenuSceneMgr.ItemDestroy(Component.CameraScene.SceneId, Component.CameraScene.SpecialCarItemId);
		Component.CameraScene.SpecialCarItemId = NullId;
	}
	Component.CameraScene.CurrentCarItemId = NullId;
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Create the car for the camera scene
K_Private_Component Private_CreateCameraScene_Car(CMlScript _Context, K_Private_Component _Component, Boolean _IsInit) {
	if (!Private_IsCameraSceneValid(_Context, _Component)) return _Component;
	if (!_IsInit && _Component.CameraScene.DefaultCarItemId != NullId && _Component.CameraScene.SpecialCarItemId != NullId) return _Component;

	declare K_Private_Component Component = Private_DestroyCameraScene_Car(_Context, _Component);

	if (Component.CameraScene.DefaultCarItemId == NullId) {
		if (_Context.LocalUser == Null) {
			Component.CameraScene.DefaultCarItemId = _Context.MenuSceneMgr.ItemCreate(
				Component.CameraScene.SceneId,
				C_Private_SportCarModelName,
				C_Private_SportCarSkinName,
				C_Private_SportCarSkinUrl
			);
		} else {
			Component.CameraScene.DefaultCarItemId = _Context.MenuSceneMgr.ItemCreate(
				Component.CameraScene.SceneId,
				C_Private_SportCarModelName,
				_Context.LocalUser.Model_CarSport_SkinName,
				_Context.LocalUser.Model_CarSport_SkinUrl,
				_Context.LocalUser.Prestige_SkinOptions
			);
		}
	}

	if (C_Private_CarCanTransform && Component.CameraScene.SpecialCarItemId == NullId) {
		if (Component.CameraScene.SpecialCarModelName == "") {
			Component.CameraScene.SpecialCarModelName = C_Private_RallyCarModelName;
			switch (ML::Rand(1, 3)) {
				case 1: Component.CameraScene.SpecialCarModelName = C_Private_RallyCarModelName;
				case 2: Component.CameraScene.SpecialCarModelName = C_Private_SnowCarModelName;
				case 3: Component.CameraScene.SpecialCarModelName = C_Private_DesertCarModelName;
			}
		}
		Component.CameraScene.SpecialCarItemId = _Context.MenuSceneMgr.ItemCreate(
			Component.CameraScene.SceneId,
			Component.CameraScene.SpecialCarModelName,
			"",
			""
		);
	}

	if (Component.CameraScene.DefaultCarItemId != NullId) {
		_Context.MenuSceneMgr.ItemSetLocation(
			Component.CameraScene.SceneId,
			Component.CameraScene.DefaultCarItemId,
			C_Private_CarPosition_SportCar,
			C_Private_CarRotation,
			C_Private_CarIsTurntable
		);
		if (_Context.LocalUser != Null) {
			_Context.MenuSceneMgr.ItemSetPlayerState(
				Component.CameraScene.SceneId,
				Component.CameraScene.DefaultCarItemId,
				_Context.LocalUser
			);
		}
	}

	if (Component.CameraScene.SpecialCarItemId != NullId) {
		_Context.MenuSceneMgr.ItemSetLocation(
			Component.CameraScene.SceneId,
			Component.CameraScene.SpecialCarItemId,
			C_Private_CarPosition_SportCar + <0., C_Private_CarBoostTransformPosY, 0.>,
			C_Private_CarRotation,
			C_Private_CarIsTurntable
		);
		if (_Context.LocalUser != Null) {
			_Context.MenuSceneMgr.ItemSetPlayerState(
				Component.CameraScene.SceneId,
				Component.CameraScene.SpecialCarItemId,
				_Context.LocalUser
			);
		}
	}

	Component.IsWaitingCarStateUpdate = True;
	Component = Private_SetIsSpecialCarDisplayed(_Context, Component, Component.CameraScene.IsSpecialCarDisplayed);

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Destroy the podium for the camera scene
K_Private_Component Private_DestroyCameraScene_Podium(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.CameraScene.SceneId == NullId || _Component.CameraScene.PodiumItemId == NullId) return _Component;

	declare K_Private_Component Component = _Component;
	_Context.MenuSceneMgr.ItemDestroy(Component.CameraScene.SceneId, Component.CameraScene.PodiumItemId);
	Component.CameraScene.PodiumItemId = NullId;
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Create the podium for the camera scene
K_Private_Component Private_CreateCameraScene_Podium(CMlScript _Context, K_Private_Component _Component, Boolean _IsInit) {
	if (!Private_IsCameraSceneValid(_Context, _Component)) return _Component;
	if (!_IsInit && _Component.CameraScene.PodiumItemId != NullId) return _Component;

	declare K_Private_Component Component = Private_DestroyCameraScene_Podium(_Context, _Component);

	if (Component.CameraScene.PodiumItemId == NullId) {
		Component.CameraScene.PodiumItemId = _Context.MenuSceneMgr.ItemCreate(
			Component.CameraScene.SceneId,
			C_Private_PodiumModelName,
			C_Private_PodiumSkinName,
			C_Private_PodiumSkinUrl
		);
	}

	if (Component.CameraScene.PodiumItemId != NullId) {
		_Context.MenuSceneMgr.ItemSetLocation(
			Component.CameraScene.SceneId,
			Component.CameraScene.PodiumItemId,
			C_Private_PodiumPosition,
			C_Private_PodiumRotation,
			False
		);
		_Context.MenuSceneMgr.PodiumTweakShadingSet(
			Component.CameraScene.SceneId,
			C_Private_PodiumBaseColor,
			C_Private_PodiumCubeReflect,
			C_Private_PodiumPlaneReflect
		);
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Destroy the scene for the camera scene
K_Private_Component Private_DestroyCameraScene_Scene(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.CameraScene.SceneId == NullId) return _Component;

	declare K_Private_Component Component = _Component;
	Component = Private_DestroyCameraScene_Pilot(_Context, Component);
	Component = Private_DestroyCameraScene_Car(_Context, Component);
	Component = Private_DestroyCameraScene_Podium(_Context, Component);
	_Context.MenuSceneMgr.SceneDestroy(Component.CameraScene.SceneId);
	Component.CameraScene.SceneId = NullId;
	Private_UpdateCameraSceneVisibility(Component);
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Create the scene for the camera scene
K_Private_Component Private_CreateCameraScene_Scene(CMlScript _Context, K_Private_Component _Component, Boolean _IsInit) {
	if (!_IsInit && _Component.CameraScene.SceneId != NullId) return _Component;

	declare K_Private_Component Component = Private_DestroyCameraScene_Scene(_Context, _Component);

	if (Component.CameraScene.SceneId == NullId) {
		Component.CameraScene.SceneId = _Context.MenuSceneMgr.SceneCreate(C_Private_SceneLayout);
	}
	MenuSceneManager::PageSynchronizeScene(_Context.Page, _Context.LocalUser);

	if (Component.CameraScene.SceneId != NullId) {
		_Context.MenuSceneMgr.PostFxSet(Component.CameraScene.SceneId, C_Private_ScenePostFx);
		_Context.MenuSceneMgr.CameraSetLocation(
			Component.CameraScene.SceneId,
			C_Private_CameraPosition,
			C_Private_CameraRotation,
			C_Private_CameraFOV
		);

		_Context.MenuSceneMgr.LightDir0Set(
			Component.CameraScene.SceneId,
			C_Private_LightRGB,
			C_Private_LightIntensity,
			C_Private_LightAltitudeDeg,
			C_Private_LightAzimuteDeg
		);

		_Context.MenuSceneMgr.PlaneReflectRefresh();

		Component = Private_UpdateCameraSceneCubeMapNextFrame(Component);
	}

	Private_UpdateCameraSceneVisibility(Component);

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Destroy the camera scene (podium + car + pilot)
K_Private_Component Private_DestroyCameraScene(CMlScript _Context, K_Private_Component _Component) {
	declare K_Private_Component Component = _Component;

	Component = Private_DestroyCameraScene_Pilot(_Context, Component);
	Component = Private_DestroyCameraScene_Car(_Context, Component);
	Component = Private_DestroyCameraScene_Podium(_Context, Component);
	Component = Private_DestroyCameraScene_Scene(_Context, Component);

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Create the camera scene (podium + car + pilot)
K_Private_Component Private_CreateCameraScene(CMlScript _Context, K_Private_Component _Component, Boolean _IsInit) {
	declare K_Private_Component Component = _Component;

	Component = Private_CreateCameraScene_Scene(_Context, Component, _IsInit);
	Component = Private_CreateCameraScene_Podium(_Context, Component, _IsInit);
	Component = Private_CreateCameraScene_Car(_Context, Component, _IsInit);
	Component = Private_CreateCameraScene_Pilot(_Context, Component, _IsInit);

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Recreate the camera scene if it was destroyed automatically by the C++ at some points
K_Private_Component Private_ReloadCameraScene(CMlScript _Context, K_Private_Component _Component, Integer _CameraSceneReloadCounter) {
	declare K_Private_Component Component = _Component;

	Component.CameraScene.ReloadCounter = _CameraSceneReloadCounter;

	Component = Private_DestroyCameraScene(_Context, Component);
	Component = Private_CreateCameraScene(_Context, Component, True);

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Get the car boost level
Integer Private_GetCarBoostLevel(K_Private_Component _Component) {
	if (
		_Component.CarBoost == C_Private_CarBoost_HoverUp ||
		_Component.CarBoost == C_Private_CarBoost_HoverLoop ||
		_Component.CarBoost == C_Private_CarBoost_TransformUp || (
			_Component.CarBoost == C_Private_CarBoost_TransformDown &&
			_Component.CarBoostAnim.CurrentValue > 0.1
		)
	) {
		return C_Private_CarBoostLevel_Up;
	}
	return C_Private_CarBoostLevel_Off;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Update the car state
K_Private_Component Private_UpdateCarState(CMlScript _Context, K_Private_Component _Component) {
	if (
		!_Component.IsWaitingCarStateUpdate ||
		!Private_IsCameraSceneValid(_Context, _Component) ||
		_Component.CameraScene.CurrentCarItemId == NullId
	) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;

	_Context.MenuSceneMgr.ItemSetVehicleState(
		Component.CameraScene.SceneId,
		Component.CameraScene.CurrentCarItemId,
		Component.CarSteerAnim.CurrentValue,
		C_Private_CarBrakeLightEnabled,
		C_Private_CarFrontLightEnabled,
		C_Private_CarTurboLevel,
		Private_GetCarBoostLevel(Component),
		C_Private_CarBurnoutSmokeEnabled
	);
	_Context.MenuSceneMgr.ItemSetLocation(
		Component.CameraScene.SceneId,
		Component.CameraScene.CurrentCarItemId,
		Component.CarPosition + <0., Component.CarBoostAnim.CurrentValue, 0.>,
		C_Private_CarRotation,
		C_Private_CarIsTurntable
	);

	Component.IsWaitingCarStateUpdate = False;

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Create an animation structure
K_Private_Anim Private_CreateAnim(Real _CurrentValue, Real _TargetValue, Integer _Duration) {
	return K_Private_Anim {
		StartTime = Now,
		EndTime = Now + _Duration,
		Duration = _Duration,
		CurrentValue = _CurrentValue,
		StartValue = _CurrentValue,
		TargetValue = _TargetValue,
		DeltaValue = _TargetValue - _CurrentValue
	};
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the car steer direction
K_Private_Component Private_SetCarSteerDirection(K_Private_Component _Component, Integer _CarSteerDirection, Boolean _IsInit) {
	if (!_IsInit && _Component.CarSteerDirection == _CarSteerDirection) return _Component;

	declare K_Private_Component Component = _Component;

	Component.CarSteerDirection = _CarSteerDirection;
	Component.CarSteerAnim = Private_CreateAnim(
		Component.CarSteerAnim.CurrentValue,
		C_Private_CarSteerAngleList.get(Component.CarSteerDirection, C_Private_CarSteerAngleList[C_Private_CarSteerDirection_Neutral]),
		C_Private_CarSteerAnimDuration
	);

	return Component;
}
K_Private_Component Private_SetCarSteerDirection(K_Private_Component _Component, Integer _CarSteerDirection) {
	return Private_SetCarSteerDirection(_Component, _CarSteerDirection, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the car steer direction from the player's inputs
K_Private_Component Private_SetCarSteerDirection(K_Private_Component _Component, CMlScriptEvent::EMenuNavAction _MenuNavAction) {
	if (
		(_Component.CarSteerDirection == C_Private_CarSteerDirection_Left && _MenuNavAction == CMlScriptEvent::EMenuNavAction::Left) ||
		(_Component.CarSteerDirection == C_Private_CarSteerDirection_Right && _MenuNavAction == CMlScriptEvent::EMenuNavAction::Right)
	) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;

	switch (Component.CarSteerDirection) {
		case C_Private_CarSteerDirection_Left: {
			if (_MenuNavAction == CMlScriptEvent::EMenuNavAction::Right) {
				Component = Private_SetCarSteerDirection(Component, C_Private_CarSteerDirection_Neutral);
			}
		}
		case C_Private_CarSteerDirection_Neutral: {
			if (_MenuNavAction == CMlScriptEvent::EMenuNavAction::Right) {
				Component = Private_SetCarSteerDirection(Component, C_Private_CarSteerDirection_Right);
			} else if (_MenuNavAction == CMlScriptEvent::EMenuNavAction::Left) {
				Component = Private_SetCarSteerDirection(Component, C_Private_CarSteerDirection_Left);
			}
		}
		case C_Private_CarSteerDirection_Right: {
			if (_MenuNavAction == CMlScriptEvent::EMenuNavAction::Left) {
				Component = Private_SetCarSteerDirection(Component, C_Private_CarSteerDirection_Neutral);
			}
		}
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Update the car steer animation
K_Private_Component Private_UpdateCarSteerAnim(K_Private_Component _Component) {
	if (_Component.CarSteerAnim.StartTime < 0) return _Component;

	declare K_Private_Component Component = _Component;

	if (Component.CarSteerAnim.EndTime <= Now) {
		Component.CarSteerAnim.StartTime = -1;
		Component.CarSteerAnim.CurrentValue = Component.CarSteerAnim.TargetValue;
		Component.IsWaitingCarStateUpdate = True;
	} else {
		Component.CarSteerAnim.CurrentValue = AL::EaseInOutQuad(
			Now - Component.CarSteerAnim.StartTime,
			Component.CarSteerAnim.StartValue,
			Component.CarSteerAnim.DeltaValue,
			Component.CarSteerAnim.Duration
		);
		Component.IsWaitingCarStateUpdate = True;
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the car boost
K_Private_Component Private_SetCarBoost(K_Private_Component _Component, Integer _CarBoost, Boolean _IsInit) {
	if (!_IsInit && _Component.CarBoost == _CarBoost) return _Component;

	declare K_Private_Component Component = _Component;

	Component.CarBoost = _CarBoost;
	if (Component.CarBoost == C_Private_CarBoost_TransformUp) {
		Component.CarBoostTransformCooldownEndTime = Now + C_Private_CarBoostTransformCooldownDuration;
	}
	Component.CarBoostHoverCooldownEndTime = Now + C_Private_CarBoostHoverCooldownDuration;

	switch (Component.CarBoost) {
		case C_Private_CarBoost_HoverUp: {
			Component.CarBoostAnim = Private_CreateAnim(
				Component.CarBoostAnim.CurrentValue,
				C_Private_CarBoostHoverPosY,
				C_Private_CarBoostHoverAnimDuration
			);
		}
		case C_Private_CarBoost_HoverDown: {
			Component.CarBoostAnim = Private_CreateAnim(
				Component.CarBoostAnim.CurrentValue,
				C_Private_CarBoostOffPosY,
				C_Private_CarBoostHoverAnimDuration
			);
		}
		case C_Private_CarBoost_HoverLoop: {
			Component.CarBoostAnim = Private_CreateAnim(
				Component.CarBoostAnim.CurrentValue,
				C_Private_CarBoostHoverPosY,
				0
			);
		}
		case C_Private_CarBoost_TransformUp: {
			Component.CarBoostAnim = Private_CreateAnim(
				Component.CarBoostAnim.CurrentValue,
				C_Private_CarBoostTransformPosY,
				C_Private_CarBoostTransformAnimDuration
			);
		}
		case C_Private_CarBoost_TransformDown: {
			Component.CarBoostAnim = Private_CreateAnim(
				Component.CarBoostAnim.CurrentValue,
				C_Private_CarBoostOffPosY,
				C_Private_CarBoostTransformAnimDuration
			);
		}
	}

	return Component;
}
K_Private_Component Private_SetCarBoost(K_Private_Component _Component, Integer _CarBoost) {
	return Private_SetCarBoost(_Component, _CarBoost, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the car boost from the player's inputs
K_Private_Component Private_SetCarBoost(K_Private_Component _Component, CMlScriptEvent::EMenuNavAction _MenuNavAction) {
	if (_Component.CarBoostHoverCooldownEndTime > Now ||
		_Component.CarBoost == C_Private_CarBoost_TransformUp ||
		_Component.CarBoost == C_Private_CarBoost_TransformDown ||
		_Component.CarBoost == C_Private_CarBoost_HoverUp ||
		_Component.CarBoost == C_Private_CarBoost_HoverDown ||
		(_Component.CarBoost == C_Private_CarBoost_Off && _MenuNavAction == CMlScriptEvent::EMenuNavAction::Down)
	) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;

	switch (Component.CarBoost) {
		case C_Private_CarBoost_Off: {
			if (_MenuNavAction == CMlScriptEvent::EMenuNavAction::Up) {
				if (
					C_Private_CarBoostTransformProbability > 0. &&
					Component.CarBoostTransformCooldownEndTime <= Now &&
					ML::Rand(0., 100.) <= C_Private_CarBoostTransformProbability
				) {
					Component = Private_SetCarBoost(Component, C_Private_CarBoost_TransformUp);
				} else {
					Component = Private_SetCarBoost(Component, C_Private_CarBoost_HoverUp);
				}
			}
		}
		case C_Private_CarBoost_HoverLoop: {
			if (_MenuNavAction == CMlScriptEvent::EMenuNavAction::Down) {
				Component = Private_SetCarBoost(Component, C_Private_CarBoost_HoverDown);
			} else if (
				_MenuNavAction == CMlScriptEvent::EMenuNavAction::Up &&
				C_Private_CarBoostTransformProbability > 0. &&
				Component.CarBoostTransformCooldownEndTime <= Now &&
				ML::Rand(0., 100.) <= C_Private_CarBoostTransformProbability
			) {
				Component = Private_SetCarBoost(Component, C_Private_CarBoost_TransformUp);
			}
		}
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Update the car boost animation
K_Private_Component Private_UpdateCarBoostAnim(CMlScript _Context, K_Private_Component _Component) {
	if (_Component.CarBoostAnim.StartTime < 0) return _Component;

	declare K_Private_Component Component = _Component;

	switch (Component.CarBoost) {
		case C_Private_CarBoost_HoverUp, C_Private_CarBoost_TransformUp: {
			if (Component.CarBoostAnim.EndTime <= Now) {
				Component.CarBoostAnim.StartTime = -1;
				Component.CarBoostAnim.CurrentValue = Component.CarBoostAnim.TargetValue;
				Component.IsWaitingCarStateUpdate = True;
				if (Component.CarBoost == C_Private_CarBoost_HoverUp) {
					Component = Private_SetCarBoost(Component, C_Private_CarBoost_HoverLoop);
				} else {
					Component = Private_SetIsSpecialCarDisplayed(_Context, Component, !Component.CameraScene.IsSpecialCarDisplayed);
					Component = Private_SetCarBoost(Component, C_Private_CarBoost_TransformDown);
				}
			} else {
				Component.CarBoostAnim.CurrentValue = AL::EaseInOutQuad(
					Now - Component.CarBoostAnim.StartTime,
					Component.CarBoostAnim.StartValue,
					Component.CarBoostAnim.DeltaValue,
					Component.CarBoostAnim.Duration
				);
				Component.IsWaitingCarStateUpdate = True;
			}
		}
		case C_Private_CarBoost_HoverDown: {
			if (Component.CarBoostAnim.EndTime <= Now) {
				Component.CarBoostAnim.StartTime = -1;
				Component.CarBoostAnim.CurrentValue = Component.CarBoostAnim.TargetValue;
				Component.IsWaitingCarStateUpdate = True;
				Component = Private_SetCarBoost(Component, C_Private_CarBoost_Off);
			} else {
				Component.CarBoostAnim.CurrentValue = AL::EaseOutBounce(
					Now - Component.CarBoostAnim.StartTime,
					Component.CarBoostAnim.StartValue,
					Component.CarBoostAnim.DeltaValue,
					Component.CarBoostAnim.Duration
				);
				Component.IsWaitingCarStateUpdate = True;
			}
		}
		case C_Private_CarBoost_HoverLoop: {
			Component.CarBoostAnim.CurrentValue = C_Private_CarBoostHoverPosY + (C_Private_CarBoostHoverAnimAmplitude * (ML::Sin((Now - Component.CarBoostAnim.StartTime) * C_Private_CarBoostHoverAnimFrequency)));
			Component.IsWaitingCarStateUpdate = True;
		}
		case C_Private_CarBoost_TransformDown: {
			if (Component.CarBoostAnim.EndTime <= Now) {
				Component.CarBoostAnim.StartTime = -1;
				Component.CarBoostAnim.CurrentValue = Component.CarBoostAnim.TargetValue;
				Component.IsWaitingCarStateUpdate = True;
				Component = Private_SetCarBoost(Component, C_Private_CarBoost_Off);
			} else {
				Component.CarBoostAnim.CurrentValue = AL::EaseOutQuart(
					Now - Component.CarBoostAnim.StartTime,
					Component.CarBoostAnim.StartValue,
					Component.CarBoostAnim.DeltaValue,
					Component.CarBoostAnim.Duration
				);
				Component.IsWaitingCarStateUpdate = True;
			}
		}
	}

	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Play an emote animation on the pilot
K_Private_Component Private_PlayPilotEmote(CMlScript _Context, K_Private_Component _Component) {
	if (
		!Private_IsCameraSceneValid(_Context, _Component) ||
		_Component.CameraScene.PilotItemId == NullId ||
		_Component.PilotEmoteCooldownEndTime > Now
	) {
		return _Component;
	}

	declare K_Private_Component Component = _Component;
	Component.PilotEmoteCooldownEndTime = Now + C_Private_PilotEmoteCooldownDuration;
	_Context.MenuSceneMgr.ItemTriggerPlayerGesture(Component.CameraScene.SceneId, Component.CameraScene.PilotItemId);
	return Component;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Enable or disable the component
K_Private_Component Private_SetIsEnabled(CMlScript _Context, K_Private_Component _Component, Boolean _IsEnabled, Boolean _IsInit) {
	if (!_IsInit && _Component.IsEnabled == _IsEnabled) return _Component;

	declare K_Private_Component Component = _Component;

	Component.IsEnabled = _IsEnabled;

	declare CManiaApp ParentApp <=> Context::GetParentApp(_Context);
	assert(ParentApp != Null && (ParentApp is CManiaAppTitle), """[HomeBackground@2] Unsupported context""");
	declare CManiaAppTitle ParentAppTitle <=> (ParentApp as CManiaAppTitle);
	if (Component.IsEnabled) {
		Component = Private_CreateCameraScene(_Context, Component, _IsInit);
		ParentAppTitle.FillExtraSpaceWithBluredContents_Opacity = 1.;
	} else {
		ParentAppTitle.FillExtraSpaceWithBluredContents_Opacity = 0.;
	}

	Private_SetBackgroundVideoIsPlaying(_Context, Component, Component.IsEnabled);
	Private_UpdateCameraSceneVisibility(Component);

	declare Integer Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter for _Context.System = 0;
	Component.CameraScene.ReloadCounter = Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter;

	return Component;
}
K_Private_Component Private_SetIsEnabled(CMlScript _Context, K_Private_Component _Component, Boolean _IsEnabled) {
	return Private_SetIsEnabled(_Context, _Component, _IsEnabled, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Enable or disable the inputs in the component
K_Private_Component Private_SetIsInputEnabled(K_Private_Component _Component, Boolean _IsInputEnabled, Boolean _IsInit) {
	if (!_IsInit && _Component.IsInputEnabled == _IsInputEnabled) return _Component;
	declare K_Private_Component Component = _Component;
	Component.IsInputEnabled = _IsInputEnabled;
	return Component;
}
K_Private_Component Private_SetIsInputEnabled(K_Private_Component _Component, Boolean _IsInputEnabled) {
	return Private_SetIsInputEnabled(_Component, _IsInputEnabled, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Set the active background video and foreground image retrieved from the API
K_Private_Component Private_SetMediaActive(
	CMlScript _Context,
	K_Private_Component _Component,
	Integer _MenuMediaUpdate,
	CampaignStore::K_MediaActive _BackgroundVideo,
	CampaignStore::K_MediaActive _ForegroundImage,
	Boolean _IsInit
) {
	if (!_IsInit && _Component.MenuMediaUpdate == _MenuMediaUpdate) return _Component;

	declare K_Private_Component Component = _Component;

	Component.MenuMediaUpdate = _MenuMediaUpdate;
	if (_BackgroundVideo.Url != "" && _Context.Http.IsValidUrl(_BackgroundVideo.Url) && _BackgroundVideo.IsVideo) {
		Component = Private_SetBackgroundVideo(_Context, Component, _BackgroundVideo.Url);
	} else {
		Component = Private_SetBackgroundVideo(_Context, Component, C_Private_DefaultBackgroundVideo);
	}

	if (_ForegroundImage.Url != "" && _Context.Http.IsValidUrl(_ForegroundImage.Url)) {
		Component = Private_SetForegroundImage(_Context, Component, _ForegroundImage.Url);
	} else {
		Component = Private_SetForegroundImage(_Context, Component, C_Private_DefaultForegroundImage);
	}

	return Component;
}
K_Private_Component Private_SetMediaActive(
	CMlScript _Context,
	K_Private_Component _Component,
	Integer _MenuMediaUpdate,
	CampaignStore::K_MediaActive _BackgroundVideo,
	CampaignStore::K_MediaActive _ForegroundImage
) {
	return Private_SetMediaActive(_Context, _Component, _MenuMediaUpdate, _BackgroundVideo, _ForegroundImage, False);
}

/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Initialize the component instance
Void Private_InitComponentInstance(CMlScript _Context, CMlFrame _Frame_HomeBackground, Text _Url, Boolean _IsVideo) {
	declare K_Private_Component Trackmania_ComponentsHomeBackground2_Component as Component for _Frame_HomeBackground = C_Private_Component_Null;
	Component = C_Private_Component_Null;
	Component.CarSteerAnim = C_Private_Anim_Null;
	Component.CarBoostAnim = C_Private_Anim_Null;

	Component.Controls.Frame_HomeBackground = _Frame_HomeBackground;
	Component.Controls.Quad_BackgroundVideoTransition = (Component.Controls.Frame_HomeBackground.GetFirstChild(C_Private_Id_Quad_BackgroundVideoTransition) as CMlQuad);
	Component.Controls.Quad_BackgroundVideoActive = (Component.Controls.Frame_HomeBackground.GetFirstChild(C_Private_Id_Quad_BackgroundVideoActive) as CMlQuad);
	Component.Controls.Quad_ForegroundImageTransition = (Component.Controls.Frame_HomeBackground.GetFirstChild(C_Private_Id_Quad_ForegroundImageTransition) as CMlQuad);
	Component.Controls.Quad_ForegroundImageActive = (Component.Controls.Frame_HomeBackground.GetFirstChild(C_Private_Id_Quad_ForegroundImageActive) as CMlQuad);
	Component.Controls.Camera_Scene = (Component.Controls.Frame_HomeBackground.GetFirstChild(C_Private_Id_Camera_Scene) as CMlCamera);

	declare CampaignStore::K_MediaActive NewMediaActive = CampaignStore::K_MediaActive {
		Type = 8,
		Url = _Url,
		IsVideo = _IsVideo
	};
	if (_Url == "") NewMediaActive = CampaignStore::GetMediaActive(CampaignStore::C_CampaignMediaType_HomeBackground);
	Component = Private_SetIsEnabled(_Context, Component, False, True);
	Component = Private_SetIsInputEnabled(Component, False, True);
	Component = Private_SetCarSteerDirection(Component, C_Private_CarSteerDirection_Neutral, True);
	Component = Private_SetCarBoost(Component, C_Private_CarBoost_Off, True);
	Component = Private_SetBackgroundVideoTransitionState(_Context, Component, C_Private_TransitionState_Complete, True);
	Component = Private_SetBackgroundVideo(_Context, Component, C_Private_DefaultBackgroundVideo, True);
	Component = Private_SetForegroundImageTransitionState(_Context, Component, C_Private_TransitionState_Complete, True);
	Component = Private_SetForegroundImage(_Context, Component, C_Private_DefaultForegroundImage, True);
	Component = Private_SetMediaActive(_Context, Component, CampaignStore::GetMenuMediaUpdate(), NewMediaActive, CampaignStore::GetMediaActive(CampaignStore::C_CampaignMediaType_HomeForeground), True);
	Component = Private_UpdateCarState(_Context, Component);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// `MainInit` section of the component
Void Private_MainInit(CMlScript _Context, Text _Url, Boolean _IsVideo) {
	declare CMlFrame[] Trackmania_ComponentsHomeBackground2_HomeBackgroundList for _Context.Page = [];
	Trackmania_ComponentsHomeBackground2_HomeBackgroundList = [];
	_Context.Page.GetClassChildren(C_Private_Class_ComponentContainer, _Context.Page.MainFrame, True);
	foreach (Control in _Context.Page.GetClassChildren_Result) {
		declare CMlFrame Frame_ComponentContainer = (Control as CMlFrame);
		Trackmania_ComponentsHomeBackground2_HomeBackgroundList.add(Frame_ComponentContainer.Parent);
		Private_InitComponentInstance(_Context, Frame_ComponentContainer.Parent, _Url, _IsVideo);
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// `MainLoop` section of the component
Void Private_MainLoop(CMlScript _Context, Text _Url, Boolean _IsVideo) {
	if (_Context.PageIsVisible) {
		declare CMlFrame[] Trackmania_ComponentsHomeBackground2_HomeBackgroundList for _Context.Page = [];
		for (Frame_HomeBackground in Trackmania_ComponentsHomeBackground2_HomeBackgroundList) {
			declare K_Private_Component Trackmania_ComponentsHomeBackground2_Component as Component for Frame_HomeBackground = C_Private_Component_Null;

			if (
				Component.CameraScene.SceneId != NullId &&
				MenuSceneManager::PageNeedToRecreateScene(_Context.Page, _Context.LocalUser)
			) {
				declare Integer Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter for _Context.System = 0;
				Component = Private_ReloadCameraScene(_Context, Component, Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter);
			}

			if (Component.UpdateCameraSceneCubeMapTime != -1 && Component.UpdateCameraSceneCubeMapTime < Now) {
				Component.UpdateCameraSceneCubeMapTime = -1;
				Private_UpdateCameraSceneCubeMap(_Context, Component);
			}

			if (Component.IsEnabled) {
				declare Integer Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter for _Context.System = 0;
				declare CampaignStore::K_MediaActive NewMediaActive = CampaignStore::K_MediaActive {
					Type = 8,
					Url = _Url,
					IsVideo = _IsVideo
				};
				if (_Url == "") NewMediaActive = CampaignStore::GetMediaActive(CampaignStore::C_CampaignMediaType_HomeBackground);
				if (Component.MenuMediaUpdate != CampaignStore::GetMenuMediaUpdate()) {
					Component = Private_SetMediaActive(_Context, Component, CampaignStore::GetMenuMediaUpdate(), NewMediaActive, CampaignStore::GetMediaActive(CampaignStore::C_CampaignMediaType_HomeForeground));
				}
				if (Component.CameraScene.ReloadCounter != Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter) {
					Component = Private_ReloadCameraScene(_Context, Component, Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter);
				}

				if (Component.IsInputEnabled) {
					foreach (Event in ShareMenuNavigation::GetPendingEvents(_Context)) {
						if (
							Event.MenuNavAction == CMlScriptEvent::EMenuNavAction::Left ||
							Event.MenuNavAction == CMlScriptEvent::EMenuNavAction::Right
						) {
							Component = Private_SetCarSteerDirection(Component, Event.MenuNavAction);
						} else if (
							Event.MenuNavAction == CMlScriptEvent::EMenuNavAction::Up ||
							Event.MenuNavAction == CMlScriptEvent::EMenuNavAction::Down
						) {
							Component = Private_PlayPilotEmote(_Context, Component);
							Component = Private_SetCarBoost(Component, Event.MenuNavAction);
						}
					}
				}

				Component = Private_UpdateCarSteerAnim(Component);
				Component = Private_UpdateCarBoostAnim(_Context, Component);
				Component = Private_UpdateCarState(_Context, Component);
				Component = Private_UpdateBackgroundVideoTransitionState(_Context, Component);
				Component = Private_UpdateForegroundImageTransitionState(_Context, Component);
			}
		}
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Public functions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Recreate the camera scene if it was destroyed automatically by the C++ at some points
Void ReloadCameraScene(CSystemPlatform _System) {
	declare Integer Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter for _System = 0;
	Trackmania_ComponentsHomeBackground2_CameraSceneReloadCounter += 1;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Display the new background video immediatly if possible
Void SkipTransition(CSystemPlatform _System) {
	declare Boolean Trackmania_ComponentsHomeBackground2_SkipBackgroundVideoTransition for _System = False;
	declare Boolean Trackmania_ComponentsHomeBackground2_SkipForegroundImageTransition for _System = False;
	Trackmania_ComponentsHomeBackground2_SkipBackgroundVideoTransition = True;
	Trackmania_ComponentsHomeBackground2_SkipForegroundImageTransition = True;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Enable or disable the component
Void SetIsEnabled(CMlScript _Context, CMlFrame _Frame_HomeBackground, Boolean _IsEnabled) {
	declare K_Private_Component Trackmania_ComponentsHomeBackground2_Component for _Frame_HomeBackground = C_Private_Component_Null;
	Trackmania_ComponentsHomeBackground2_Component = Private_SetIsEnabled(_Context, Trackmania_ComponentsHomeBackground2_Component, _IsEnabled);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Enable or disable the inputs in the component
Void SetIsInputEnabled(CMlFrame _Frame_HomeBackground, Boolean _IsInputEnabled) {
	declare K_Private_Component Trackmania_ComponentsHomeBackground2_Component for _Frame_HomeBackground = C_Private_Component_Null;
	Trackmania_ComponentsHomeBackground2_Component = Private_SetIsInputEnabled(Trackmania_ComponentsHomeBackground2_Component, _IsInputEnabled);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Update the car displayed in the scene
 *
 * @remarks Useful to update the car skin and prestige
 */
Void UpdateCar(CMlScript _Context, CMlFrame _Frame_HomeBackground) {
	declare K_Private_Component Trackmania_ComponentsHomeBackground2_Component as Component for _Frame_HomeBackground = C_Private_Component_Null;
	Component = Private_DestroyCameraScene_Car(_Context, Component);
	Component = Private_CreateCameraScene_Car(_Context, Component, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Update the pilot displayed in the scene
 *
 * @remarks Useful to update the car skin and prestige
 */
Void UpdatePilot(CMlScript _Context, CMlFrame _Frame_HomeBackground) {
	declare K_Private_Component Trackmania_ComponentsHomeBackground2_Component as Component for _Frame_HomeBackground = C_Private_Component_Null;
	Component = Private_DestroyCameraScene_Pilot(_Context, Component);
	Component = Private_CreateCameraScene_Pilot(_Context, Component, False);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Get the component
Text ComponentV2() {
	return MV::ComponentV2(
		C_Name,
		Private_GetManialink(),
		"""
#Include "Libs/Nadeo/Trackmania/Components/HomeBackground@2.Script.txt" as {{{Q}}}
		""",
		"""
***MainInit***
***
{{{P}}}Private_MainInit(This);
***

***MainLoop***
***
{{{P}}}Private_MainLoop(This);
***
""",
		[
			ShareMenuNavigation::ComponentV2()
		],
		[]
	);
}