/**
 *	WarmUp lib
 */
#Const	Version		"2017-04-18"
#Const	ScriptName	"Libs/Nadeo/WarmUp3Common.Script.txt"

// ---------------------------------- //
// Libraries
// ---------------------------------- //
#Include "TextLib" as TL
#Include "Libs/Nadeo/Utils.Script.txt" as Utils
#Include "Libs/Nadeo/XmlRpc2.Script.txt" as XmlRpc

// ---------------------------------- //
// Constant
// ---------------------------------- //
#Const C_LibWarmUp_LayerPosition <0., -50.> //< Default UI position
// XmlRpc callbacks
#Const C_Callback_WarmUp_Start		"Maniaplanet.WarmUp.Start"
#Const C_Callback_WarmUp_End			"Maniaplanet.WarmUp.End"
#Const C_Callback_WarmUp_Status	"Maniaplanet.WarmUp.Status"
// XmlRpc methods
#Const C_Method_WarmUp_Extend					"Maniaplanet.WarmUp.Extend"
#Const C_Method_WarmUp_Stop						"Maniaplanet.WarmUp.ForceStop"
#Const C_Method_WarmUp_GetStatus				"Maniaplanet.WarmUp.GetStatus"
#Const C_Method_WarmUp_BlockEndWarmUp	"Maniaplanet.WarmUp.BlockEndWarmUp"

// ---------------------------------- //
// Globales
// ---------------------------------- //
declare Boolean G_LibWarmUp_IsLoaded;
declare Ident G_LibWarmUp_LayerWarmUpId;
declare Ident[Integer][Text] G_LibWarmUp_GroupsIds;
declare Text[Integer][Text] G_LibWarmUp_GroupsLogins;
declare Text[Integer][Text] G_LibWarmUp_GroupsIcons;
declare Integer[][Integer][Text] G_LibWarmUp_GroupsTimers;
declare Integer[][Integer][Text] G_LibWarmUp_GroupsTimersXmlRpc;
declare Integer[Text] G_LibWarmUp_GroupsCurrentTimer;
declare Integer G_LibWarmUp_CurrentTimer;
declare Integer[Integer] G_LibWarmUp_SavedTimer;
declare Boolean G_LibWarmUp_Updated;
declare Boolean G_LibWarmUp_Stop;
declare Text[] G_LibWarmUp_GroupsDisabled;
declare Boolean G_LibWarmUp_IsInWarmUp;
declare Boolean G_LibWarmUp_XmlRpcControlEnabled;
declare Boolean G_LibWarmUp_XmlRpcControlUpdate;

// ---------------------------------- //
// Functions
// ---------------------------------- //

// ---------------------------------- //
// Private
// ---------------------------------- //

// ---------------------------------- //
/** Update the Server Synchro Value
 *
 *	@return		The new server synchro value
 */
Integer Private_SynchroServer() {
	declare persistent Server_SynchroValue for This = 0;
	Server_SynchroValue += 1;
	if(Server_SynchroValue > 100) Server_SynchroValue = 0;
	
	return Server_SynchroValue;
}


// ---------------------------------- //
/** Create the warm up manialink
 *
 *	@return		The warm up manialink
 */
Text Private_CreateLayerWarmUp() {
	declare ImgPath = "file://Media/Manialinks/Common/WarmUp/";
	declare ImgPath2 = "file://Media/Manialinks/ShootMania/Common/Pager/";
	declare SlotWidth = 50.;
	declare SlotHeight = 9.;
	declare SlotMargin = 0.;
	declare LabelWidth = SlotWidth - SlotHeight;
	declare IconWidth = SlotHeight;
	declare LabelPosX = IconWidth / 2.;
	declare IconPosX = -LabelWidth / 2.;
	declare Clan1Color = TL::ColorToText(Teams[0].ColorPrimary);
	declare Clan2Color = TL::ColorToText(Teams[1].ColorPrimary);
	
	return """
<manialink version="3" name="Lib_WarmUp2:WarmUp">
<frame pos="{{{C_LibWarmUp_LayerPosition.X}}} {{{C_LibWarmUp_LayerPosition.Y}}}" z-index="25" hidden="1" id="Frame_WarmUp">
	<format textemboss="1" />
	<frame pos="0 0" id="Frame_Pager">
		<quad pos="-126.5 0" size="8 6" halign="center" valign="center" image="{{{ImgPath2}}}pagerLeftOff.dds" imagefocus="{{{ImgPath2}}}pagerLeftOn.dds" scriptevents="1" id="Button_PagerPrev" />
		<quad pos="126.5 0" size="8 6" halign="center" valign="center" image="{{{ImgPath2}}}pagerRightOff.dds" imagefocus="{{{ImgPath2}}}pagerRightOn.dds" scriptevents="1" id="Button_PagerNext" />
		<quad pos="-126.5 0" size="8 6" halign="center" valign="center" image="{{{ImgPath2}}}pagerEmpty.dds" hidden="1" id="Button_PagerPrevOff" />
		<quad pos="126.5 0" size="8 6" halign="center" valign="center" image="{{{ImgPath2}}}pagerEmpty.dds" hidden="1" id="Button_PagerNextOff" />
	</frame>
	<frame pos="0 0">
		<quad pos="0 -3" z-index="2" size="284 20" halign="center" valign="center" image="{{{ImgPath}}}Structure.dds" id="Quad_OrderBg" />
		<quad pos="0 7.1" z-index="3" size="50 10" halign="center" valign="center" image="{{{ImgPath}}}TitleBg.dds" />
		<label pos="0 7.4" z-index="4" size="35 10" halign="center" valign="center" textemboss="1" textsize="1" style="TextTitle3" text="{{{_("Players order")}}}" />
		<frame id="Frame_Order">
			<format textsize="2" />
			<frame pos="-100 0" class="Frame_Slot" id="1" >
				<label pos="0 0" z-index="-1" size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center2" focusareacolor1="fff0" focusareacolor2="fff5" scriptevents="1" class="Quad_Slot" id="1" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardGreen.png" hidden="1" id="Quad_SlotReady" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardRed.png" id="Quad_SlotNotReady" />
				<quad pos="{{{IconPosX}}} 0" z-index="1" size="{{{IconWidth}}} {{{SlotHeight}}}" scale="0.7" halign="center" valign="center" hidden="1" id="Quad_Icon" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" id="Label_Slot" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" textcolor="fff3" textemboss="0" text="{{{_("First (Press 1)")}}}" id="Label_EmptySlot" />
			</frame>
			<frame pos="-50 0" class="Frame_Slot" id="2" >
				<label pos="0 0" z-index="-1" size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center2" focusareacolor1="fff0" focusareacolor2="fff5" scriptevents="1" class="Quad_Slot" id="2" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardGreen.png" hidden="1" id="Quad_SlotReady" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardRed.png" id="Quad_SlotNotReady" />
				<quad pos="{{{IconPosX}}} 0" z-index="1" size="{{{IconWidth}}} {{{SlotHeight}}}" scale="0.7" halign="center" valign="center" hidden="1" id="Quad_Icon" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" id="Label_Slot" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" textcolor="fff3" textemboss="0" text="{{{_("Second (Press 2)")}}}" id="Label_EmptySlot" />
			</frame>
			<frame pos="0 0" class="Frame_Slot" id="3" >
				<label pos="0 0" z-index="-1" size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center2" focusareacolor1="fff0" focusareacolor2="fff5" scriptevents="1" class="Quad_Slot" id="3" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardGreen.png" hidden="1" id="Quad_SlotReady" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardRed.png" id="Quad_SlotNotReady" />
				<quad pos="{{{IconPosX}}} 0" z-index="1" size="{{{IconWidth}}} {{{SlotHeight}}}" scale="0.7" halign="center" valign="center" hidden="1" id="Quad_Icon" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" id="Label_Slot" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" textcolor="fff3" textemboss="0" text="{{{_("Third (Press 3)")}}}" id="Label_EmptySlot" />
			</frame>
			<frame pos="50 0" class="Frame_Slot" id="4" >
				<label pos="0 0" z-index="-1" size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center2" focusareacolor1="fff0" focusareacolor2="fff5" scriptevents="1" class="Quad_Slot" id="4" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardGreen.png" hidden="1" id="Quad_SlotReady" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardRed.png" id="Quad_SlotNotReady" />
				<quad pos="{{{IconPosX}}} 0" z-index="1" size="{{{IconWidth}}} {{{SlotHeight}}}" scale="0.7" halign="center" valign="center" hidden="1" id="Quad_Icon" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" id="Label_Slot" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" textcolor="fff3" textemboss="0" text="{{{_("Fourth (Press 4)")}}}" id="Label_EmptySlot" />
			</frame>
			<frame pos="100 0" class="Frame_Slot" id="5" >
				<label pos="0 0" z-index="-1" size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center2" focusareacolor1="fff0" focusareacolor2="fff5" scriptevents="1" class="Quad_Slot" id="5" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardGreen.png" hidden="1" id="Quad_SlotReady" />
				<quad size="{{{SlotWidth}}} {{{SlotHeight}}}" halign="center" valign="center" image="{{{ImgPath}}}PlayerCardRed.png" id="Quad_SlotNotReady" />
				<quad pos="{{{IconPosX}}} 0" z-index="1" size="{{{IconWidth}}} {{{SlotHeight}}}" scale="0.7" halign="center" valign="center" hidden="1" id="Quad_Icon" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" id="Label_Slot" />
				<label pos="0 0.5" z-index="2" size="{{{SlotWidth}}} {{{SlotHeight}}}" scale="0.9" halign="center" valign="center2" textcolor="fff3" textemboss="0" text="{{{_("Fifth (Press 5)")}}}" id="Label_EmptySlot" />
			</frame>
		</frame>
	</frame>
	<frame pos="0 -9" z-index="3" scale="0.85" hidden="1" id="Frame_ClanSelection">
		<label pos="-47 0.2" textsize="1" size="33 10" halign="center" valign="center2" style="TextTitle3" textcolor="fff" text="Join {{{Teams[0].Name}}} (F3)" id="Label_JoinClan1" />
		<label pos="47 0.2" textsize="1" size="33 10" halign="center" valign="center2" style="TextTitle3" textcolor="fff" text="Join {{{Teams[1].Name}}} (F4)" id="Label_JoinClan2" />
		<quad pos="-47 0" size="40 10" halign="center" valign="center" image="{{{ImgPath}}}ButtonOff.dds" imagefocus="{{{ImgPath}}}ButtonOn.dds" colorize="{{{Clan1Color}}}" ScriptEvents="1" id="Button_JoinClan1" />
		<quad pos="47 0" size="40 10" halign="center" valign="center" image="{{{ImgPath}}}ButtonOff.dds" imagefocus="{{{ImgPath}}}ButtonOn.dds" colorize="{{{Clan2Color}}}" ScriptEvents="1" id="Button_JoinClan2" />
	</frame>
	<frame pos="-0.15 -10.5" z-index="3" scale="0.85" id="Frame_Ready">	
		<label size="50 10" halign="center" valign="center2" style="TextTitle3" text="Ready (F6)" />
		<quad size="60 15" halign="center" valign="center" image="{{{ImgPath}}}ButtonOff.dds" imagefocus="{{{ImgPath}}}ButtonOn.dds" ScriptEvents="1" id="Button_Ready" />
	</frame>
</frame>
<script><!--
#Include "TextLib" as TL

#Const C_UpdateInterval	250

declare Integer G_PageMin;
declare Integer G_PageMax;
declare Integer G_PageCurrent;
declare Integer G_ItemPerPage;

declare CMlFrame Frame_WarmUp;
declare CMlFrame[Integer] Frames_Slot;
declare CMlQuad Button_PagerPrev;
declare CMlQuad Button_PagerNext;
declare CMlQuad Button_PagerPrevOff;
declare CMlQuad Button_PagerNextOff;

/*CSmPlayer FindPlayerFromLogin(Text _Login) {
	if (_Login == "") return Null;
	
	foreach (Player in Players) {
		if (Player.User.Login == _Login) return Player;
	}
	
	return Null;
}*/

Void UpdateSlotsPosition(Integer _ItemsNb) {
	declare ItemWidth = {{{SlotWidth}}};
	declare ItemMargin = {{{SlotMargin}}};
	declare Width = (_ItemsNb * ItemWidth) + ((_ItemsNb - 1) * ItemMargin);
	declare X = -((Width - ItemWidth)/ 2.);
	
	Button_PagerPrev.RelativePosition_V3.X = X-(ItemWidth/2.)-8.;
	Button_PagerNext.RelativePosition_V3.X = -X+(ItemWidth/2.)+8.;
	Button_PagerPrevOff.RelativePosition_V3.X = X-(ItemWidth/2.)-8.;
	Button_PagerNextOff.RelativePosition_V3.X = -X+(ItemWidth/2.)+8.;
	
	foreach (Key => Frame_Slot in Frames_Slot) {
		Frame_Slot.RelativePosition_V3.X = X;
		
		X += ItemWidth + ItemMargin;
		if (Key <= _ItemsNb) Frame_Slot.Show();
		else Frame_Slot.Hide();
	}
	
	declare Quad_OrderBg <=> (Page.GetFirstChild("Quad_OrderBg") as CMlQuad);
	Quad_OrderBg.Size.X = Width + (2. * _ItemsNb);
	//if (Quad_OrderBg.Size.X < 120.) Quad_OrderBg.Size.X = 120.;
}

Void UpdateIcons() {
	declare netread Text[Integer][Text] Net_LibWarmUp2_GroupsIcons for Teams[0];
	declare netread Text Net_LibWarmUp2_CurrentGroup for UI;
	if (!Net_LibWarmUp2_GroupsIcons.existskey(Net_LibWarmUp2_CurrentGroup)) return;
	
	declare StartIndex = ((G_PageCurrent - 1) * G_ItemPerPage) + 1;
	declare EndIndex = StartIndex + G_ItemPerPage - 1;
	declare J = 1;
	
	for (I, StartIndex, EndIndex) {
		if (!Frames_Slot.existskey(J)) continue;
		if (!Net_LibWarmUp2_GroupsIcons[Net_LibWarmUp2_CurrentGroup].existskey(I)) continue;
		declare Icon = Net_LibWarmUp2_GroupsIcons[Net_LibWarmUp2_CurrentGroup][I];
		
		declare Quad_Icon 		<=> (Frames_Slot[J].GetFirstChild("Quad_Icon")			as CMlQuad);
		declare Label_Slot		<=> (Frames_Slot[J].GetFirstChild("Label_Slot")			as CMlLabel);
		declare Label_EmptySlot	<=> (Frames_Slot[J].GetFirstChild("Label_EmptySlot")	as CMlLabel);
		
		if (Icon != "") {
			if (!Quad_Icon.Visible) {
				Quad_Icon.Show();
				Label_Slot.RelativePosition_V3.X = {{{LabelPosX}}};
				Label_EmptySlot.RelativePosition_V3.X = {{{LabelPosX}}};
				Label_Slot.Size.X = {{{LabelWidth}}};
				Label_EmptySlot.Size.X = {{{LabelWidth}}};
				Label_Slot.RelativeScale = 0.9;
				Label_EmptySlot.RelativeScale = 0.9;
			}
			Quad_Icon.ChangeImageUrl(Icon);
		} else {
			if (Quad_Icon.Visible) {
				Quad_Icon.Hide();
				Label_Slot.RelativePosition_V3.X = 0.;
				Label_EmptySlot.RelativePosition_V3.X = 0.;
				Label_Slot.Size.X = {{{SlotWidth}}};
				Label_EmptySlot.Size.X = {{{SlotWidth}}};
				Label_Slot.RelativeScale = 1.;
				Label_EmptySlot.RelativeScale = 1.;
			}
		}
		
		J += 1;
	}
}

Void ToggleReadyDisplay(CMlFrame _Frame, Boolean _IsReady) {
	declare Quad_SlotReady		<=> (_Frame.GetFirstChild("Quad_SlotReady")		as CMlQuad);
	declare Quad_SlotNotReady	<=> (_Frame.GetFirstChild("Quad_SlotNotReady")	as CMlQuad);
	
	if (_IsReady && !Quad_SlotReady.Visible) {
		Quad_SlotReady.Show();
		Quad_SlotNotReady.Hide();
	} else if (!_IsReady && !Quad_SlotNotReady.Visible) {
		Quad_SlotNotReady.Show();
		Quad_SlotReady.Hide();
	}
}

Void SetTextEmptySlot(CMlLabel _EmptySlot, Integer _Slot) {
	declare SlotText = TL::Compose("%1 #%2", _("|Warm up|Click to take the slot"), TL::ToText(_Slot));
	switch (_Slot) {
		case 1 : SlotText = _("First (Press 1)");
		case 2 : SlotText = _("Second (Press 2)");
		case 3 : SlotText = _("Third (Press 3)");
		case 4 : SlotText = _("Fourth (Press 4)");
		case 5 : SlotText = _("Fifth (Press 5)");
		case 6 : SlotText = _("Sixth (Press 6)");
		case 7 : SlotText = _("Seventh (Press 7)");
		case 8 : SlotText = _("Eighth (Press 8)");
		case 9 : SlotText = _("Ninth (Press 9)");
	}
	_EmptySlot.SetText(SlotText);
}

Void UpdateView(Text _GroupName, Integer _PageNum) {
	declare netread Text[Integer][Text] Net_LibWarmUp2_Groups for Teams[0];
	
	if (!Net_LibWarmUp2_Groups.existskey(_GroupName)) return;
	
	declare ItemsTotal = Net_LibWarmUp2_Groups[_GroupName].count;
	G_PageMin = 1;
	G_PageMax = ItemsTotal / G_ItemPerPage;
	if (ItemsTotal % G_ItemPerPage != 0) G_PageMax += 1;
	if (G_PageCurrent > G_PageMax) G_PageCurrent = G_PageMax;
	else if (G_PageCurrent < G_PageMin) G_PageCurrent = G_PageMin;
	declare ItemsNb = G_ItemPerPage;
	if (G_PageCurrent == G_PageMax) ItemsNb = ItemsTotal % G_ItemPerPage;
	if (ItemsNb == 0) ItemsNb = G_ItemPerPage;
	
	if (G_PageMax == 1) {
		Button_PagerPrev.Hide();
		Button_PagerNext.Hide();
		Button_PagerPrevOff.Hide();
		Button_PagerNextOff.Hide();
	} else {
		Button_PagerPrev.Show();
		Button_PagerNext.Show();
		if (G_PageCurrent == G_PageMin) {
			Button_PagerPrev.Hide();
			Button_PagerNext.Show();
			Button_PagerPrevOff.Show();
			Button_PagerNextOff.Hide();
		} else if (G_PageCurrent == G_PageMax) {
			Button_PagerPrev.Show();
			Button_PagerNext.Hide();
			Button_PagerPrevOff.Hide();
			Button_PagerNextOff.Show();
		} else {
			Button_PagerPrev.Show();
			Button_PagerNext.Show();
			Button_PagerPrevOff.Hide();
			Button_PagerNextOff.Hide();
		}
	}
	
	UpdateSlotsPosition(ItemsNb);
	UpdateIcons();
	
	declare StartIndex = ((G_PageCurrent - 1) * G_ItemPerPage) + 1;
	declare EndIndex = StartIndex + G_ItemPerPage - 1;
	declare J = 1;
	
	for (I, StartIndex, EndIndex) {
		if (!Frames_Slot.existskey(J)) continue;
		declare Label_Slot		<=> (Frames_Slot[J].GetFirstChild("Label_Slot")			as CMlLabel);
		declare Label_EmptySlot	<=> (Frames_Slot[J].GetFirstChild("Label_EmptySlot")	as CMlLabel);
		declare Login for Label_Slot = "";
		
		if (Net_LibWarmUp2_Groups[_GroupName].existskey(I)) {
			Login = Net_LibWarmUp2_Groups[_GroupName][I];
			//declare Player <=> FindPlayerFromLogin(Login);
			
			declare Player <=> InputPlayer;
			Player = Null;
			foreach (TmpPlayer in Players) {
				if (TmpPlayer.User.Login == Login) {
					Player <=> TmpPlayer;
					break;
				}
			}
			
			if (Player != Null) {
				Label_Slot.SetText(Player.User.Name);
				if (Label_EmptySlot.Visible) Label_EmptySlot.Hide();
				declare netread Boolean Net_LibWarmUp2_IsReadyServer for Player;
				ToggleReadyDisplay(Frames_Slot[J], Net_LibWarmUp2_IsReadyServer);
			} else {
				Label_Slot.SetText(Login);
				if (Login == "") {
					SetTextEmptySlot(Label_EmptySlot, I);
					if (!Label_EmptySlot.Visible) Label_EmptySlot.Show();
				} else {
					if (Label_EmptySlot.Visible) Label_EmptySlot.Hide();
				}
				ToggleReadyDisplay(Frames_Slot[J], False);
			}
		} else {
			Label_Slot.SetText("");
			SetTextEmptySlot(Label_EmptySlot, I);
			if (!Label_EmptySlot.Visible) Label_EmptySlot.Show();
			ToggleReadyDisplay(Frames_Slot[J], False);
		}
		J += 1;
	}
}

Void ToggleWarmUpDisplay() {
	if (IsSpectatorMode) {
		if (Frame_WarmUp.Visible) Frame_WarmUp.Hide();
	} else {
		declare netread Text	Net_LibWarmUp2_CurrentGroup		for UI;
		declare netread Text[]	Net_LibWarmUp2_GroupsDisabled	for Teams[0];
		
		if (Net_LibWarmUp2_GroupsDisabled.exists(Net_LibWarmUp2_CurrentGroup)) {
			if (Frame_WarmUp.Visible) Frame_WarmUp.Hide();
		} else {
			if (!Frame_WarmUp.Visible) Frame_WarmUp.Show();
		}
	}
}

main() {
	declare Frame_Order		<=> (Page.GetFirstChild("Frame_Order")	as CMlFrame);
	foreach (Control in Frame_Order.Controls) {
		declare Frame_Slot <=> (Control as CMlFrame);
		if (Frame_Slot == Null) continue;
		declare Key = TL::ToInteger(Frame_Slot.ControlId);
		Frames_Slot[Key] = Frame_Slot;
	}
	Frame_WarmUp		<=> (Page.GetFirstChild("Frame_WarmUp")			as CMlFrame);
	Button_PagerPrev	<=> (Page.GetFirstChild("Button_PagerPrev")		as CMlQuad);
	Button_PagerNext	<=> (Page.GetFirstChild("Button_PagerNext")		as CMlQuad);
	Button_PagerPrevOff	<=> (Page.GetFirstChild("Button_PagerPrevOff")	as CMlQuad);
	Button_PagerNextOff	<=> (Page.GetFirstChild("Button_PagerNextOff")	as CMlQuad);
	declare Frame_ClanSelection	<=> (Page.GetFirstChild("Frame_ClanSelection")	as CMlFrame);
	declare Label_JoinClan1	<=> (Page.GetFirstChild("Label_JoinClan1")	as CMlLabel);
	declare Label_JoinClan2	<=> (Page.GetFirstChild("Label_JoinClan2")	as CMlLabel);
	declare Button_JoinClan1	<=> (Page.GetFirstChild("Button_JoinClan1")	as CMlQuad);
	declare Button_JoinClan2	<=> (Page.GetFirstChild("Button_JoinClan2")	as CMlQuad);
	
	declare netread Integer Net_LibWarmUp2_SynchroServer		for Teams[0];
	declare netread Boolean	Net_LibWarmUp2_DisplayClanSelection	for Teams[0];
	declare netread Integer	Net_LibWarmUp2_GroupsUpdate			for Teams[0];
	declare netread Text	Net_LibWarmUp2_CurrentGroup			for UI;
	declare netread Integer	Net_LibWarmUp2_IconUpdate			for Teams[0];
	declare netread Vec2	Net_LibWarmUp2_LayerPosition		for Teams[0];
	declare netread Integer	Net_LibWarmUp2_GroupsDisabledUpdate	for Teams[0];
	declare netread Text[]	Net_LibWarmUp2_GroupsDisabled		for Teams[0];
	
	declare netwrite Integer Net_LibWarmUp2_SynchroClient for UI;
	declare netwrite Integer Net_LibWarmUp2_SlotUpdate for UI;
	declare netwrite Integer Net_LibWarmUp2_Slot for UI;
	declare netwrite Boolean Net_LibWarmUp2_IsReadyClient for UI;
	
	Net_LibWarmUp2_SynchroClient = -1;
	Net_LibWarmUp2_SlotUpdate = 0;
	Net_LibWarmUp2_Slot = 0;
	Net_LibWarmUp2_IsReadyClient = False;
	
	G_PageMin = 1;
	G_PageMax = 1;
	G_PageCurrent = 1;
	G_ItemPerPage = Frames_Slot.count;
	
	declare NextUpdate = 0;
	declare IsReady = False;
	
	declare PrevDisplayClanSelection = False;
	declare PrevGroupsUpdate = 0;
	declare PrevGroup = "";
	declare PrevClan1Name = "";
	declare PrevClan2Name = "";
	declare PrevClan1Color = <0., 0., 0.>;
	declare PrevClan2Color = <0., 0., 0.>;
	declare PrevIsSpectatorMode = False;
	declare PrevIconUpdate = 0;
	declare PrevLayerPosition = {{{C_LibWarmUp_LayerPosition}}};
	declare PrevGroupsDisabledUpdate = 0;
	
	while (True) {
		yield;
		
		if (InputPlayer == Null) continue;
		if (!PageIsVisible) continue;
		
		if (Now >= NextUpdate) {
			NextUpdate = Now + C_UpdateInterval;
			
			if (PrevIsSpectatorMode != IsSpectatorMode) {
				PrevIsSpectatorMode = IsSpectatorMode;
				
				ToggleWarmUpDisplay();
			}
			
			if (Net_LibWarmUp2_SynchroClient != Net_LibWarmUp2_SynchroServer) {
				Net_LibWarmUp2_SynchroClient = Net_LibWarmUp2_SynchroServer;
				
				Net_LibWarmUp2_IsReadyClient = False;
				IsReady = False;
			}
			
			if (PrevDisplayClanSelection != Net_LibWarmUp2_DisplayClanSelection) {
				PrevDisplayClanSelection = Net_LibWarmUp2_DisplayClanSelection;
				Frame_ClanSelection.Visible = PrevDisplayClanSelection;
			}
			
			if (PrevGroupsUpdate != Net_LibWarmUp2_GroupsUpdate) {
				PrevGroupsUpdate = Net_LibWarmUp2_GroupsUpdate;
				
				UpdateView(Net_LibWarmUp2_CurrentGroup, G_PageCurrent);
			}
			
			if (PrevGroup != Net_LibWarmUp2_CurrentGroup) {
				PrevGroup = Net_LibWarmUp2_CurrentGroup;
				
				G_PageCurrent = 1;
				UpdateView(Net_LibWarmUp2_CurrentGroup, G_PageCurrent);
				ToggleWarmUpDisplay();
			}
			
			if (PrevIconUpdate != Net_LibWarmUp2_IconUpdate) {
				PrevIconUpdate = Net_LibWarmUp2_IconUpdate;
				UpdateIcons();
			}
			
			if (PrevClan1Name != Teams[0].Name) {
				PrevClan1Name = Teams[0].Name;
				Label_JoinClan1.SetText("(F3) Join "^PrevClan1Name);
			}
			if (PrevClan2Name != Teams[1].Name) {
				PrevClan2Name = Teams[1].Name;
				Label_JoinClan2.SetText("(F4) Join "^PrevClan2Name);
			}
			
			if (PrevClan1Color != Teams[0].ColorPrimary) {
				PrevClan1Color = Teams[0].ColorPrimary;
				Button_JoinClan1.Colorize = PrevClan1Color;
			}
			if (PrevClan2Color != Teams[1].ColorPrimary) {
				PrevClan2Color = Teams[1].ColorPrimary;
				Button_JoinClan2.Colorize = PrevClan2Color;
			}
			
			if (PrevLayerPosition != Net_LibWarmUp2_LayerPosition) {
				PrevLayerPosition = Net_LibWarmUp2_LayerPosition;
				
				Frame_WarmUp.RelativePosition_V3.X = PrevLayerPosition.X;
				Frame_WarmUp.PosnY = PrevLayerPosition.Y;
			}
			
			if (PrevGroupsDisabledUpdate != Net_LibWarmUp2_GroupsDisabledUpdate) {
				PrevGroupsDisabledUpdate = Net_LibWarmUp2_GroupsDisabledUpdate;
				
				ToggleWarmUpDisplay();
			}
			
			foreach (Player in Players) {
				declare netread Boolean Net_LibWarmUp2_IsReadyServer for Player;
				declare Prev_LibWarmUp2_IsReadyServer for Player = False;
				if (Prev_LibWarmUp2_IsReadyServer != Net_LibWarmUp2_IsReadyServer) {
					Prev_LibWarmUp2_IsReadyServer = Net_LibWarmUp2_IsReadyServer;
					
					declare netread Text[Integer][Text] Net_LibWarmUp2_Groups for Teams[0];
					if (
						Net_LibWarmUp2_Groups.existskey(Net_LibWarmUp2_CurrentGroup) 
						&& Net_LibWarmUp2_Groups[Net_LibWarmUp2_CurrentGroup].exists(Player.Login)
					) {
						UpdateView(Net_LibWarmUp2_CurrentGroup, G_PageCurrent);
					}
				}
			}
		}
		
		foreach (Event in PendingEvents) {
			switch (Event.Type) {
				case CMlEvent::Type::MouseClick: {
					switch (Event.ControlId) {
						case "Button_JoinClan1": {
							JoinTeam1();
						}
						case "Button_JoinClan2": {
							JoinTeam2();
						}
						case "Button_PagerNext": {
							if (G_PageCurrent + 1 <= G_PageMax) G_PageCurrent += 1;
							UpdateView(Net_LibWarmUp2_CurrentGroup, G_PageCurrent);
						}
						case "Button_PagerPrev": {
							if (G_PageCurrent - 1 >= G_PageMin) G_PageCurrent -= 1;
							UpdateView(Net_LibWarmUp2_CurrentGroup, G_PageCurrent);
						}
						case "Button_Ready": {
							IsReady = !IsReady;
							Net_LibWarmUp2_IsReadyClient = IsReady;
						}
						default: {
							if (Event.Control.HasClass("Quad_Slot")) {
								Net_LibWarmUp2_SlotUpdate = Now;
								Net_LibWarmUp2_Slot = TL::ToInteger(Event.ControlId) + ((G_PageCurrent - 1) * G_ItemPerPage);
							}
						}
					}
				}
				case CMlEvent::Type::KeyPress: {
					if (Event.KeyName == "F3") {
						JoinTeam1();
					} else if (Event.KeyName == "F4") {
						JoinTeam2();
					} else if (Event.KeyName == "F6") {
						IsReady = !IsReady;
						Net_LibWarmUp2_IsReadyClient = IsReady;
					} else if (Event.KeyName == "1" || Event.KeyName == "Numpad1") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 1;
					} else if (Event.KeyName == "2" || Event.KeyName == "Numpad2") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 2;
					} else if (Event.KeyName == "3" || Event.KeyName == "Numpad3") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 3;
					} else if (Event.KeyName == "4" || Event.KeyName == "Numpad4") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 4;
					} else if (Event.KeyName == "5" || Event.KeyName == "Numpad5") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 5;
					} else if (Event.KeyName == "6" || Event.KeyName == "Numpad6") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 6;
					} else if (Event.KeyName == "7" || Event.KeyName == "Numpad7") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 7;
					} else if (Event.KeyName == "8" || Event.KeyName == "Numpad8") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 8;
					} else if (Event.KeyName == "9" || Event.KeyName == "Numpad9") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = 9;
					} else if (Event.KeyName == "0" || Event.KeyName == "Numpad0") {
						Net_LibWarmUp2_SlotUpdate = Now;
						Net_LibWarmUp2_Slot = -1;
					}
				}
			}
		}
	}
}
--></script>
</manialink>
""";
}

// ---------------------------------- //
// Send the players orders to the UI
Void Private_SendGroupsToUI() {
	declare netwrite Text[Integer][Text] Net_LibWarmUp2_Groups for Teams[0];
	declare netwrite Integer Net_LibWarmUp2_GroupsUpdate for Teams[0];
	
	Net_LibWarmUp2_Groups = G_LibWarmUp_GroupsLogins;
	Net_LibWarmUp2_GroupsUpdate = Now;
}

// ---------------------------------- //
// Public
// ---------------------------------- //

// ---------------------------------- //
/** Return the version number of the script
 *
 *	@return		The version number of the script
 */
Text GetScriptVersion() {
	return Version;
}

// ---------------------------------- //
/** Return the name of the script
 *
 *	@return		The name of the script
 */
Text GetScriptName() {
	return ScriptName;
}

// ---------------------------------- //
/** Enable the order selection for a group
 *
 *	@param	_GroupName		The name of the group to enable
 */
Void Enable(Text _GroupName) {
	if (!G_LibWarmUp_GroupsDisabled.exists(_GroupName)) return;
	
	declare Removed = G_LibWarmUp_GroupsDisabled.remove(_GroupName);
	
	declare netwrite Integer Net_LibWarmUp2_GroupsDisabledUpdate for Teams[0];
	declare netwrite Text[] Net_LibWarmUp2_GroupsDisabled for Teams[0];
	Net_LibWarmUp2_GroupsDisabledUpdate = Now;
	Net_LibWarmUp2_GroupsDisabled = G_LibWarmUp_GroupsDisabled;
}

// ---------------------------------- //
/** Disable the order selection for a group
 *
 *	@param	_GroupName		The name of the group to disable
 */
Void Disable(Text _GroupName) {
	if (G_LibWarmUp_GroupsDisabled.exists(_GroupName)) return;
	
	G_LibWarmUp_GroupsDisabled.add(_GroupName);
	
	declare netwrite Integer Net_LibWarmUp2_GroupsDisabledUpdate for Teams[0];
	declare netwrite Text[] Net_LibWarmUp2_GroupsDisabled for Teams[0];
	Net_LibWarmUp2_GroupsDisabledUpdate = Now;
	Net_LibWarmUp2_GroupsDisabled = G_LibWarmUp_GroupsDisabled;
}

// ---------------------------------- //
/// Rebuild the warm up UI
Void RebuildUI() {
	if (!UIManager.UILayers.existskey(G_LibWarmUp_LayerWarmUpId)) return;
	UIManager.UILayers[G_LibWarmUp_LayerWarmUpId].ManialinkPage = Private_CreateLayerWarmUp();
}

// ---------------------------------- //
/** Display the clan selection buttons
 *
 *	@param	_Display	The new display status of the clan selection buttons
 */
Void DisplayClanSelection(Boolean _Display) {
	declare netwrite Net_LibWarmUp2_DisplayClanSelection for Teams[0] = False;
	Net_LibWarmUp2_DisplayClanSelection = _Display;
}

// ---------------------------------- //
/** Set the position of the warm up layer
 *
 *	@param	_Pos	The new position
 */
Void SetLayerPosition(Vec2 _Pos) {
	declare netwrite Net_LibWarmUp2_LayerPosition for Teams[0] = C_LibWarmUp_LayerPosition;
	Net_LibWarmUp2_LayerPosition = _Pos;
}

// ---------------------------------- //
/** Display an icon in one slot of a group
 *
 *	@param	_GroupName		The name of the group to set
 *	@param	_Slot			The number of the slot to set
 *	@param	_Icon			The path to the icon to display
 */
Void SetSlotIcon(Text _GroupName, Integer _Slot, Text _Icon) {
	if (!G_LibWarmUp_GroupsIcons.existskey(_GroupName)) return;
	if (!G_LibWarmUp_GroupsIcons[_GroupName].existskey(_Slot)) return;
	
	G_LibWarmUp_GroupsIcons[_GroupName][_Slot] = _Icon;
	
	declare netwrite Integer Net_LibWarmUp2_IconUpdate for Teams[0];
	declare netwrite Text[Integer][Text] Net_LibWarmUp2_GroupsIcons for Teams[0];
	Net_LibWarmUp2_IconUpdate = Now;
	Net_LibWarmUp2_GroupsIcons = G_LibWarmUp_GroupsIcons;
}

// ---------------------------------- //
/** Display an icon in all the slots of a group
 *
 *	@param	_GroupName		The name of the group to set
 *	@param	_Icon			The path to the icon to display
 */
Void SetAllSlotsIcons(Text _GroupName, Text _Icon) {
	if (!G_LibWarmUp_GroupsIcons.existskey(_GroupName)) return;
	
	declare Group = G_LibWarmUp_GroupsIcons[_GroupName];
	foreach (Slot => Icon in Group) {
		G_LibWarmUp_GroupsIcons[_GroupName][Slot] = _Icon;
	}
	
	declare netwrite Integer Net_LibWarmUp2_IconUpdate for Teams[0];
	declare netwrite Text[Integer][Text] Net_LibWarmUp2_GroupsIcons for Teams[0];
	Net_LibWarmUp2_IconUpdate = Now;
	Net_LibWarmUp2_GroupsIcons = G_LibWarmUp_GroupsIcons;
}

// ---------------------------------- //
/** Check if the order or ready state was updated
 *	since the last time we called this function
 *
 *	@return		True if it was updated, false otherwise
 */
Boolean Updated() {
	if (!G_LibWarmUp_Updated) return False;
	
	G_LibWarmUp_Updated = False;
	return True;
}

// ---------------------------------- //
/** Check if a player is ready
 *
 *	@param	_Player		The player to check
 *
 *	@return		True if the player is ready, false otherwise
 */
Boolean IsReady(CPlayer _Player) {
	if (_Player == Null) return False;
	if (_Player.User.IsFakeUser) return True;
	
	declare UI <=> UIManager.GetUI(_Player);
	if (UI == Null) return True;
	
	declare netwrite Integer Net_LibWarmUp2_SynchroServer for Teams[0];
	declare netread Integer Net_LibWarmUp2_SynchroClient for UI;
	declare netread Boolean Net_LibWarmUp2_IsReadyClient for UI;
	
	if (Net_LibWarmUp2_SynchroServer == Net_LibWarmUp2_SynchroClient && Net_LibWarmUp2_IsReadyClient) return True;
	
	return False;
}

// ---------------------------------- //
/** Check if a player is ready
 *
 *	@param	_PlayerId		The id of the player to check
 *
 *	@return		True if the player is ready, false otherwise
 */
Boolean IsReady(Ident _PlayerId) {
	if (Players.existskey(_PlayerId)) return IsReady(Players[_PlayerId]);
	
	return False;
}

// ---------------------------------- //
/** Move a player in a group
 *
 *	@param	_Player		The player to remove
 *	@param	_GroupName	The group where to move the player
 */
Void SetPlayerGroup(CPlayer _Player, Text _GroupName) {
	if (_Player == Null) return;
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return;
	
	declare Text LibWarmUp2_CurrentGroup for _Player;
	
	if (LibWarmUp2_CurrentGroup == _GroupName) return;
	
	// Remove the player from his old group
	if (
		LibWarmUp2_CurrentGroup != ""
		&& G_LibWarmUp_GroupsIds.existskey(LibWarmUp2_CurrentGroup)
		&& G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].exists(_Player.Id)
	) {
		declare Slot = G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].keyof(_Player.Id);
		G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][Slot] = NullId;
		G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][Slot] = "";
	}
	
	LibWarmUp2_CurrentGroup = _GroupName;
	
	declare UI <=> UIManager.GetUI(_Player);
	if (UI != Null) {
		declare netwrite Text Net_LibWarmUp2_CurrentGroup for UI;
		Net_LibWarmUp2_CurrentGroup = LibWarmUp2_CurrentGroup;
	}
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Remove a player from his current group
 *
 *	@param	The player to remove
 */
Void UnsetPlayerGroup(CPlayer _Player) {
	declare Text LibWarmUp2_CurrentGroup for _Player;
	
	// Remove the player from his old group
	if (
		LibWarmUp2_CurrentGroup != ""
		&& G_LibWarmUp_GroupsIds.existskey(LibWarmUp2_CurrentGroup)
		&& G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].exists(_Player.Id)
	) {
		declare Slot = G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].keyof(_Player.Id);
		G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][Slot] = NullId;
		G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][Slot] = "";
	}
	
	LibWarmUp2_CurrentGroup = "";
	
	declare UI <=> UIManager.GetUI(_Player);
	if (UI != Null) {
		declare netwrite Text Net_LibWarmUp2_CurrentGroup for UI;
		Net_LibWarmUp2_CurrentGroup = LibWarmUp2_CurrentGroup;
	}
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Get in which group a player is
 *
 *	@param	_Player		The player to check
 *
 *	@return		The name of the group if the player has one, an empty Text otherwise
 */
Text GetPlayerGroup(CPlayer _Player) {
	declare Text LibWarmUp2_CurrentGroup for _Player;
	return LibWarmUp2_CurrentGroup;
}

// ---------------------------------- //
/** Set a player in a slot of his group
 *	If the set is not forced the player can take the slot
 *	only if it's empty or if the player in the slot is not ready.
 *	If the player in the slot is ready the other player must already
 *	have a slot in the group to wamp with him.
 *
 *	@param	_Player		The player to set
 *	@param	_Slot		The slot for this player
 *	@param	_Forced		If False, the slot must meet certain conditions before the player can take it
 */
Void SetPlayerSlot(CPlayer _Player, Integer _Slot, Boolean _Forced) {
	if (_Player == Null) return;
	declare Text LibWarmUp2_CurrentGroup for _Player;
	if (!G_LibWarmUp_GroupsIds.existskey(LibWarmUp2_CurrentGroup)) return;
	if (!G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].existskey(_Slot)) return;
	
	// Check if the player was already in a slot
	declare PrevSlot = -1;
	if (G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].exists(_Player.Id)) {
		PrevSlot = G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].keyof(_Player.Id);
	}
	// Check if the new slot is occupied
	declare OtherId = NullId;
	declare OtherLogin = "";
	if (G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][_Slot] != NullId) {
		OtherId = G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][_Slot];
		OtherLogin = G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][_Slot];
	}
	
	if (!_Forced && OtherId != NullId) {
		if (PrevSlot <= 0 && IsReady(OtherId)) return;
	}
	
	// Update the previous slot of the player
	if (PrevSlot > 0) {
		G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][PrevSlot] = OtherId;
		G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][PrevSlot] = OtherLogin;
	}
	
	G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][_Slot] = _Player.Id;
	G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][_Slot] = _Player.User.Login;
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
// Overload of the SetPlayerSlot function
Void SetPlayerSlot(CPlayer _Player, Integer _Slot) {
	SetPlayerSlot(_Player, _Slot, False);
}

// ---------------------------------- //
/** Get the current slot of a player
 *
 *	@param	_Player		The player to check
 *
 *	@return		The slot of the player if he has one, -1 otherwise
 */
Integer GetPlayerSlot(CPlayer _Player) {
	if (_Player == Null) return -1;
	declare Text LibWarmUp2_CurrentGroup for _Player;
	if (!G_LibWarmUp_GroupsIds.existskey(LibWarmUp2_CurrentGroup)) return -1;
	if (!G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].exists(_Player.Id)) return -1;
	
	return G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].keyof(_Player.Id);
}

// ---------------------------------- //
/** Unset the slot of a player
 *
 *	@param	_Player		The player to unset
 */
Void UnsetPlayerSlot(CPlayer _Player) {
	if (_Player == Null) return;
	declare Text LibWarmUp2_CurrentGroup for _Player;
	if (!G_LibWarmUp_GroupsIds.existskey(LibWarmUp2_CurrentGroup)) return;
	if (!G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].exists(_Player.Id)) return;
	
	declare Slot = G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup].keyof(_Player.Id);
	G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][Slot] = NullId;
	G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][Slot] = "";
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Get the content of a slot
 *
 *	@param	_GroupName		The group to check
 *	@param	_Slot			The slot to check
 *
 *	@return		The id of the player in the slot if there is one, NullId otherwise
 */
Ident GetSlot(Text _GroupName, Integer _Slot) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return NullId;
	if (!G_LibWarmUp_GroupsIds[_GroupName].existskey(_Slot)) return NullId;
	
	return G_LibWarmUp_GroupsIds[_GroupName][_Slot];
}

// ---------------------------------- //
/** Unset a player from a slot
 *
 *	@param	_GroupName	The name of the group where the slot must be unset
 *	@param	_Slot		The slot to unset
 */
Void UnsetSlot(Text _GroupName, Integer _Slot) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return;
	if (!G_LibWarmUp_GroupsIds[_GroupName].existskey(_Slot)) return;
	
	G_LibWarmUp_GroupsIds[_GroupName][_Slot] = NullId;
	G_LibWarmUp_GroupsLogins[_GroupName][_Slot] = "";
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Check if a group exists
 *
 *	@param	_GroupName		The name of the group
 *
 *	@return		True if the gorup exists, False otherwise
 */
Boolean GroupExists(Text _GroupName) {
	return G_LibWarmUp_GroupsIds.existskey(_GroupName);
}

// ---------------------------------- //
/** Create a new warm up group
 *
 *	@param	_GroupName		The name of the group
 *	@param	_SlotsNb		The number of slots in the group
 */
Void CreateGroup(Text _GroupName, Integer _SlotsNb) {
	if (_SlotsNb < 1) return;
	if (G_LibWarmUp_GroupsIds.existskey(_GroupName)) return;
	
	G_LibWarmUp_GroupsIds[_GroupName] = [];
	G_LibWarmUp_GroupsLogins[_GroupName] = [];
	G_LibWarmUp_GroupsIcons[_GroupName] = [];
	
	for (I, 1, _SlotsNb) {
		G_LibWarmUp_GroupsIds[_GroupName][I] = NullId;
		G_LibWarmUp_GroupsLogins[_GroupName][I] = "";
		G_LibWarmUp_GroupsIcons[_GroupName][I] = "";
	}
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Destroy a warm up group
 *
 *	@param	_GroupName		The name of the group to destroy
 */
Void DestroyGroup(Text _GroupName) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return;
	
	// Remove the players from this group
	foreach (Player in AllPlayers) {
		if (GetPlayerGroup(Player) == _GroupName) {
			UnsetPlayerGroup(Player);
		}
	}
	
	declare Removed = G_LibWarmUp_GroupsIds.removekey(_GroupName);
	Removed = G_LibWarmUp_GroupsLogins.removekey(_GroupName);
	Removed = G_LibWarmUp_GroupsIcons.removekey(_GroupName);
	Removed = G_LibWarmUp_GroupsTimers.removekey(_GroupName);
	Removed = G_LibWarmUp_GroupsTimersXmlRpc.removekey(_GroupName);
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Manually set the players ids in a group
 *
 *	@param	_GroupName		The name of the group to set
 *	@param	_PlayersIds		The players ids to add
 */
Void SetGroup(Text _GroupName, Ident[Integer] _PlayersIds) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return;
	declare Group = G_LibWarmUp_GroupsIds[_GroupName];
	
	foreach (Slot => PlayerId in Group) {
		if (_PlayersIds.existskey(Slot)) {
			if (!Players.existskey(_PlayersIds[Slot])) continue;
			declare Player <=> Players[_PlayersIds[Slot]];
			SetPlayerGroup(Player, _GroupName);
			SetPlayerSlot(Player, Slot, True);
		} else {
			UnsetSlot(_GroupName, Slot);
		}
	}
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Get the players ids of a group
 *
 *	@param	_GroupName		The name of the group to get
 *
 *	@return		The ordered players ids
 */
Ident[Integer] GetGroup(Text _GroupName) {
	declare Ident[Integer] Empty;
	
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return Empty;
	
	return G_LibWarmUp_GroupsIds[_GroupName];
}

// ---------------------------------- //
/** Update the number of slots available in a group
 *
 *	@param	_GroupName		The name of the group to update
 *	@param	_SlotsNb		The new number  of slots
 */
Void SetSlotsNb(Text _GroupName, Integer _SlotsNb) {
	if (_SlotsNb < 1) return;
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return;
	if (G_LibWarmUp_GroupsIds[_GroupName].count == _SlotsNb) return;
	
	if (G_LibWarmUp_GroupsIds[_GroupName].count > _SlotsNb) {
		for (I, _SlotsNb+1, G_LibWarmUp_GroupsIds[_GroupName].count) {
			declare Removed = G_LibWarmUp_GroupsIds[_GroupName].removekey(I);
			Removed = G_LibWarmUp_GroupsLogins[_GroupName].removekey(I);
			Removed = G_LibWarmUp_GroupsIcons[_GroupName].removekey(I);
		}
	} else {
		for (I, G_LibWarmUp_GroupsIds[_GroupName].count+1, _SlotsNb) {
			G_LibWarmUp_GroupsIds[_GroupName][I] = NullId;
			G_LibWarmUp_GroupsLogins[_GroupName][I] = "";
			G_LibWarmUp_GroupsIcons[_GroupName][I] = "";
		}
	}
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/** Get the number of slots in a group
 *
 *	@param	_GroupName		The name of the group to check
 *
 *	@return		The number of slot in the group if this group exist, 0 otherwise
 */
Integer GetSlotsNb(Text _GroupName) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return 0;
	
	return G_LibWarmUp_GroupsIds[_GroupName].count;
}

// ---------------------------------- //
/** Get the number of ready players in a group
 *
 *	@param	_GroupName		The name of the group to check
 *
 *	@return		The number of ready players
 */
Integer GetReadyPlayersNb(Text _GroupName) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return 0;
	
	declare ReadyNb = 0;
	foreach (PlayerId in G_LibWarmUp_GroupsIds[_GroupName]) {
		if (IsReady(PlayerId)) ReadyNb += 1;
	}
	
	return ReadyNb;
}

// ---------------------------------- //
/** Get the number of players in a group
 *
 *	@param	_GroupName		The name of the group to check
 *
 *	@return		The number of players
 */
Integer GetPlayersNb(Text _GroupName) {
	if (!G_LibWarmUp_GroupsIds.existskey(_GroupName)) return 0;
	
	declare PlayersNb = 0;
	foreach (PlayerId in G_LibWarmUp_GroupsIds[_GroupName]) {
		if (PlayerId != NullId) PlayersNb += 1;
	}
	
	return PlayersNb;
}

// ---------------------------------- //
// Automatically fill the orders with players
Void Fill() {
	foreach (Player in Players) {
		declare Text LibWarmUp2_CurrentGroup for Player;
		if (!G_LibWarmUp_GroupsIds.existskey(LibWarmUp2_CurrentGroup)) continue;		
		declare Group = G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup];
		if (Group.exists(Player.Id)) continue;
		
		foreach (Slot => PlayerId in Group) {
			if (PlayerId != NullId) continue;
			
			G_LibWarmUp_GroupsIds[LibWarmUp2_CurrentGroup][Slot] = Player.Id;
			G_LibWarmUp_GroupsLogins[LibWarmUp2_CurrentGroup][Slot] = Player.User.Login;
			break;
		}
	}
	
	G_LibWarmUp_Updated = True;
	Private_SendGroupsToUI();
}

// ---------------------------------- //
/// Remove the players from the order when they stop playing
Void Clean() {
	declare Integer[Text] ToRemove;
	foreach (GroupName => GroupOrder in G_LibWarmUp_GroupsIds) {
		foreach (Slot => PlayerId in GroupOrder) {
			if (PlayerId == NullId) continue;
			if (!Players.existskey(PlayerId)) {
				ToRemove[GroupName] = Slot;
			}
		}
	}
	foreach (GroupName => Slot in ToRemove) {
		G_LibWarmUp_GroupsIds[GroupName][Slot] = NullId;
		G_LibWarmUp_GroupsLogins[GroupName][Slot] = "";
	}
	if (ToRemove.count > 0) {
		G_LibWarmUp_Updated = True;
		Private_SendGroupsToUI();
	}
}

// ---------------------------------- //
/** Set a group timers
 *	@param	_GroupName		The name of the group to update
 *	@param	_Timers			The new timers with this format :
 *							[Timer => [MinPlayerReady, MinPlayer]]
 *							If MinPlayerReady < 0 then the value will be replaced
 *							by the current number of players in the group
 *							(meaning that all the players of the group must be ready)
 */
Void SetGroupTimers(Text _GroupName, Integer[][Integer] _Timers) {
	if (!GroupExists(_GroupName)) return;
	
	foreach (PlayersNb in _Timers) {
		if (PlayersNb.count != 2) return;
	}
	G_LibWarmUp_GroupsTimers[_GroupName] = _Timers;
	G_LibWarmUp_GroupsCurrentTimer[_GroupName] = -1;
}

// ---------------------------------- //
/// Attach the warm up layer
Void Attach() {
	if (
		UIManager.UILayers.existskey(G_LibWarmUp_LayerWarmUpId) 
		&& !UIManager.UIAll.UILayers.existskey(G_LibWarmUp_LayerWarmUpId)
	) {
		UIManager.UIAll.UILayers.add(UIManager.UILayers[G_LibWarmUp_LayerWarmUpId]);
	}
}

// ---------------------------------- //
/// Detach the warm up layer
Void Detach() {
	declare Removed = UIManager.UIAll.UILayers.removekey(G_LibWarmUp_LayerWarmUpId);
}

// ---------------------------------- //
/// Initialize the warm up
Void Begin() {
	XmlRpc::SendCallback(C_Callback_WarmUp_Start, ["{}"]);
	
	Attach();
	
	declare netwrite Integer Net_LibWarmUp2_SynchroServer for Teams[0];
	Net_LibWarmUp2_SynchroServer = Private_SynchroServer();
	
	foreach (GroupName => GroupIds in G_LibWarmUp_GroupsIds) {
		if (G_LibWarmUp_GroupsCurrentTimer.existskey(GroupName)) {
			G_LibWarmUp_GroupsCurrentTimer[GroupName] = -1;
		}
	}
	G_LibWarmUp_CurrentTimer = -1;
	G_LibWarmUp_SavedTimer.clear();
	G_LibWarmUp_Updated = True;
	G_LibWarmUp_Stop = False;
	G_LibWarmUp_IsInWarmUp = True;
}

// ---------------------------------- //
/// Clean after the warm up
Void End() {	
	Detach();
	G_LibWarmUp_Stop = False;
	G_LibWarmUp_IsInWarmUp = False;
	
	XmlRpc::SendCallback(C_Callback_WarmUp_End, ["{}"]);
}

// ---------------------------------- //
/** Warm up loop
 *	@param	_EndTime	The current end time
 *
 *	@return 			The new end time
 */
Integer Loop(Integer _EndTime) {
	declare CommonEndTime = _EndTime;
	
	// Check the players requests
	foreach (Player in Players) {
		declare Text LibWarmUp2_CurrentGroup for Player;
		if (G_LibWarmUp_GroupsDisabled.exists(LibWarmUp2_CurrentGroup)) continue;
		
		declare UI <=> UIManager.GetUI(Player);
		if (UI == Null) continue;
		
		declare LibWarmUp2_PrevSlotUpdate for Player = 0;
		declare netread Integer Net_LibWarmUp2_SlotUpdate for UI;
		if (LibWarmUp2_PrevSlotUpdate != Net_LibWarmUp2_SlotUpdate) {
			LibWarmUp2_PrevSlotUpdate = Net_LibWarmUp2_SlotUpdate;
			
			declare netread Integer Net_LibWarmUp2_Slot for UI;
			if (Net_LibWarmUp2_Slot > 0) {
				SetPlayerSlot(Player, Net_LibWarmUp2_Slot);
			} else if (Net_LibWarmUp2_Slot < 0) {
				UnsetPlayerSlot(Player);
			}
		}
		
		declare netwrite Net_LibWarmUp2_IsReadyServer for Player = False;
		declare netread Boolean Net_LibWarmUp2_IsReadyClient for UI;
		if (Player.User.IsFakeUser && !Net_LibWarmUp2_IsReadyServer) {
			Net_LibWarmUp2_IsReadyServer = True;
			G_LibWarmUp_Updated = True;
		} else if (!Player.User.IsFakeUser && Net_LibWarmUp2_IsReadyServer != Net_LibWarmUp2_IsReadyClient) {
			Net_LibWarmUp2_IsReadyServer = Net_LibWarmUp2_IsReadyClient;
			G_LibWarmUp_Updated = True;
		}
	}
	
	// Timer management
	declare NeedUpdate = False;
	declare Timers = G_LibWarmUp_GroupsTimers;
	if (G_LibWarmUp_XmlRpcControlEnabled) {
		Timers = G_LibWarmUp_GroupsTimersXmlRpc;
	}
	if (G_LibWarmUp_XmlRpcControlUpdate) {
		NeedUpdate = True;
		G_LibWarmUp_XmlRpcControlUpdate = False;
	}
	
	foreach (GroupName => Timers in Timers) {
		declare I = 0;
		foreach (Timer => PlayersNb in Timers) {
			declare PlayersReadyTarget = PlayersNb[0];
			declare PlayersTotalTarget = PlayersNb[1];
			declare PlayersReadyCurrent = GetReadyPlayersNb(GroupName);
			declare PlayersTotalCurrent = GetPlayersNb(GroupName);
			if (PlayersReadyTarget < 0) PlayersReadyTarget = PlayersTotalCurrent;
			if (PlayersTotalTarget < 0) PlayersTotalTarget = PlayersTotalCurrent;
			
			if (PlayersReadyCurrent >= PlayersReadyTarget && PlayersTotalCurrent >= PlayersTotalTarget) {
				if (G_LibWarmUp_GroupsCurrentTimer[GroupName] != Timer) {
					G_LibWarmUp_GroupsCurrentTimer[GroupName] = Timer;
					NeedUpdate = True;
				}
				break;
			}
			I += 1;
			if (I == Timers.count) {
				if (G_LibWarmUp_GroupsCurrentTimer[GroupName] != -1) {
					G_LibWarmUp_GroupsCurrentTimer[GroupName] = -1;
					NeedUpdate = True;
				}
			}
		}
	}
	
	if (NeedUpdate) {
		declare NewTimer = -1;
		foreach (GroupName => Timer in G_LibWarmUp_GroupsCurrentTimer) {
			if (Timer == -1) {
				NewTimer = -1;
				break;
			}
			if (Timer > NewTimer) NewTimer = Timer;
		}
		
		if (G_LibWarmUp_CurrentTimer != NewTimer) {
			declare OverrideEndTime = -1;
			if (NewTimer > 0 && NewTimer < G_LibWarmUp_CurrentTimer) {
				G_LibWarmUp_SavedTimer.clear();
				G_LibWarmUp_SavedTimer[G_LibWarmUp_CurrentTimer] = CommonEndTime;
			} else if (G_LibWarmUp_SavedTimer.existskey(NewTimer)) {
				OverrideEndTime = G_LibWarmUp_SavedTimer[NewTimer];
				G_LibWarmUp_SavedTimer.clear();
			} else {
				G_LibWarmUp_SavedTimer.clear();
			}
			G_LibWarmUp_CurrentTimer = NewTimer;
			
			if (OverrideEndTime >= 0) CommonEndTime = OverrideEndTime;
			else if (G_LibWarmUp_CurrentTimer >= 0) CommonEndTime = Now + G_LibWarmUp_CurrentTimer;
			else CommonEndTime = -1;
		}
	}
	
	if (CommonEndTime > 0 && CommonEndTime <= Now) G_LibWarmUp_Stop = True;
	
	// XmlRpc commands
	foreach (Event in XmlRpc.PendingEvents) {
		if (Event.Type == CXmlRpcEvent::EType::CallbackArray) {
			switch (Event.ParamArray1) {
				case C_Method_WarmUp_Extend: {
					declare ExtendTime = 0;
					if (Event.ParamArray2.existskey(0)) ExtendTime = TL::ToInteger(Event.ParamArray2[0]);
					if (CommonEndTime >= 0) {
						CommonEndTime += ExtendTime;
						if (G_LibWarmUp_SavedTimer.count > 0) {
							declare Key = -1;
							foreach (Timer => PrevEndTime in G_LibWarmUp_SavedTimer) { Key = Timer; break; }
							G_LibWarmUp_SavedTimer[Key] += ExtendTime;
						}
					}
				}
				case C_Method_WarmUp_Stop: {
					G_LibWarmUp_Stop = True;
				}
			}
		}
	}
		
	// Cleaning
	Clean();
	
	return CommonEndTime;
}

// ---------------------------------- //
/** Say if the warm up must stop or not
 *
 *	@return		True if the warm up must stop, false if it cans continue
 */
Boolean Stop() {
	if (ServerShutdownRequested || MatchEndRequested) return True;
	
	return G_LibWarmUp_Stop;
}

// ---------------------------------- //
/** Check if the library is loaded
 *
 *	@return														True if the library is loaded
 *																		False otherwise
 */
Boolean IsLoaded() {
	return G_LibWarmUp_IsLoaded;
}

// ---------------------------------- //
/** Check if there is an ongoing warmup
 *
 *	@return														True if a warmup is ongoind
 *																		False otherwise
 */
Boolean IsActive() {
	return G_LibWarmUp_IsInWarmUp;
}

// ---------------------------------- //
/** Send a callback with the warm up status
 *
 *	@param	_ResponseId								The responseid of the callback
 *	@param	_IsLoaded									Is the library loaded or not
 *	@param	_IsActive									Is there an ongoing warm up or not
 */
Void SendStatusCallback(Text _ResponseId, Boolean _IsLoaded, Boolean _IsActive) {
	XmlRpc::SendCallback(C_Callback_WarmUp_Status, ["""{
	"responseid": {{{dump(_ResponseId)}}},
	"available": {{{XmlRpc::JsonGetBoolean(_IsLoaded)}}},
	"active": {{{XmlRpc::JsonGetBoolean(_IsActive)}}}
}"""]);
}

// ---------------------------------- //
/// Library update
Void Yield() {
	// XmlRpc commands
	foreach (Event in XmlRpc.PendingEvents) {
		if (Event.Type == CXmlRpcEvent::EType::CallbackArray) {
			switch (Event.ParamArray1) {
				case C_Method_WarmUp_BlockEndWarmUp: {
					declare Timer = 5000;
					if (Event.ParamArray2.existskey(0)) G_LibWarmUp_XmlRpcControlEnabled = Utils::ToBoolean(Event.ParamArray2[0]);
					if (Event.ParamArray2.existskey(1)) Timer = TL::ToInteger(Event.ParamArray2[1]);
					
					// Reset timers
					G_LibWarmUp_XmlRpcControlUpdate = True;
					G_LibWarmUp_GroupsTimersXmlRpc.clear();
					G_LibWarmUp_SavedTimer.clear();
					foreach (GroupName => GroupIds in G_LibWarmUp_GroupsIds) {
						if (G_LibWarmUp_GroupsCurrentTimer.existskey(GroupName)) {
							G_LibWarmUp_GroupsCurrentTimer[GroupName] = -1;
						}
						if (Timer > 0) G_LibWarmUp_GroupsTimersXmlRpc[GroupName] = [Timer => [-1, 1]];
					}
				}
			}
		}
	}
}

// ---------------------------------- //
/** Unregister basic XmlRpc callbacks and methods
 *	that can be used outside the library
 */
Void UnregisterBaseXmlRpc() {
	XmlRpc::UnregisterCallback(C_Callback_WarmUp_Status);
	XmlRpc::UnregisterMethod(C_Method_WarmUp_GetStatus);
}

// ---------------------------------- //
/** Register basic XmlRpc callbacks and methods
 *	that can be used outside the library
 */
Void RegisterBaseXmlRpc() {
	XmlRpc::RegisterCallback(C_Callback_WarmUp_Status, """
* Name: {{{C_Callback_WarmUp_Status}}}
* Type: CallbackArray
* Description: The status of the warmup.
* Data:
	- Version >=2.0.0:
	```
	[
		"{
			"responseid": "xyz", //< Facultative id passed by a script event
			"available": true, //< true if a warmup is available in the game mode, false otherwise
			"active": true //< true if a warmup is ongoing, false otherwise
		}"
	]
	```
""");

	XmlRpc::RegisterMethod(C_Method_WarmUp_GetStatus, """
* Name: {{{C_Method_WarmUp_GetStatus}}}
* Type: TriggerModeScriptEventArray
* Description: Get the status of the warmup.
* Data:
	- Version >=2.0.0:
	```
	[
		"responseid" //< Facultative id that will be passed to the "{{{C_Callback_WarmUp_Status}}}" callback.
	]
	```
""");
}

// ---------------------------------- //
/// Unload the library
Void Unload() {
	if (UIManager.UILayers.existskey(G_LibWarmUp_LayerWarmUpId)) {
		UIManager.UILayerDestroy(UIManager.UILayers[G_LibWarmUp_LayerWarmUpId]);
		G_LibWarmUp_LayerWarmUpId = NullId;
	}
	
	declare netwrite Text[Integer][Text] Net_LibWarmUp2_Groups for Teams[0];
	declare netwrite Integer Net_LibWarmUp2_GroupsUpdate for Teams[0];
	declare netwrite Integer Net_LibWarmUp2_SynchroServer for Teams[0];
	declare netwrite Boolean Net_LibWarmUp2_DisplayClanSelection for Teams[0];
	declare netwrite Integer Net_LibWarmUp2_GroupsDisabledUpdate for Teams[0];
	declare netwrite Text[] Net_LibWarmUp2_GroupsDisabled for Teams[0];
	Net_LibWarmUp2_Groups = [];
	Net_LibWarmUp2_GroupsUpdate = 0;
	Net_LibWarmUp2_SynchroServer = Private_SynchroServer();
	Net_LibWarmUp2_DisplayClanSelection = False;
	Net_LibWarmUp2_GroupsDisabledUpdate = -1;
	Net_LibWarmUp2_GroupsDisabled = [];
	
	foreach (Player in AllPlayers) {
		declare netwrite Net_LibWarmUp2_IsReadyServer for Player = False;
		Net_LibWarmUp2_IsReadyServer = False;
		
		declare LibWarmUp2_CurrentGroup for Player = "";
		LibWarmUp2_CurrentGroup = "";
		
		declare UI <=> UIManager.GetUI(Player);
		if (UI != Null) {
			declare netwrite Text Net_LibWarmUp2_CurrentGroup for UI;
			Net_LibWarmUp2_CurrentGroup = "";
		}
	}
	
	G_LibWarmUp_GroupsIds.clear();
	G_LibWarmUp_GroupsLogins.clear();
	G_LibWarmUp_GroupsIcons.clear();
	G_LibWarmUp_Updated = True;
	G_LibWarmUp_GroupsDisabled.clear();
	G_LibWarmUp_GroupsTimers.clear();
	G_LibWarmUp_GroupsTimersXmlRpc.clear();
	G_LibWarmUp_GroupsCurrentTimer.clear();
	G_LibWarmUp_CurrentTimer = -1;
	G_LibWarmUp_SavedTimer.clear();
	G_LibWarmUp_IsInWarmUp = False;
	G_LibWarmUp_XmlRpcControlEnabled = False;
	G_LibWarmUp_XmlRpcControlUpdate = False;
	
	XmlRpc::UnregisterCallback(C_Callback_WarmUp_Start);
	XmlRpc::UnregisterCallback(C_Callback_WarmUp_End);
	
	XmlRpc::UnregisterMethod(C_Method_WarmUp_Extend);
	XmlRpc::UnregisterMethod(C_Method_WarmUp_Stop);
	XmlRpc::UnregisterMethod(C_Method_WarmUp_BlockEndWarmUp);
	
	G_LibWarmUp_IsLoaded = False;
}

// ---------------------------------- //
/// Load the library
Void Load() {
	Unload();
	
	G_LibWarmUp_IsLoaded = True;
	
	// Create and assign the layer
	declare LayerWarmUp <=> UIManager.UILayerCreate();
	LayerWarmUp.ManialinkPage = Private_CreateLayerWarmUp();
	G_LibWarmUp_LayerWarmUpId = LayerWarmUp.Id;
	
	declare netwrite Net_LibWarmUp2_LayerPosition for Teams[0] = C_LibWarmUp_LayerPosition;
	Net_LibWarmUp2_LayerPosition = C_LibWarmUp_LayerPosition;
	
	XmlRpc::RegisterCallback(C_Callback_WarmUp_Start, """
* Name: {{{C_Callback_WarmUp_Start}}}
* Type: CallbackArray
* Description: Callback sent when the warmup starts.
* Data:
	- Version >=2.0.0:
	```
	[
		"{}"
	]
	```
""");
	XmlRpc::RegisterCallback(C_Callback_WarmUp_End, """
* Name: {{{C_Callback_WarmUp_End}}}
* Type: CallbackArray
* Description: Callback sent when the warmup ends.
* Data:
	- Version >=2.0.0:
	```
	[
		"{}"
	]
	```
""");

	XmlRpc::RegisterMethod(C_Method_WarmUp_Extend, """
* Name: {{{C_Method_WarmUp_Extend}}}
* Type: TriggerModeScriptEventArray
* Description: Extend the duration of any ongoing warmup.
* Data:
	- Version >=2.0.0:
	```
	[
		"60000" //< the duration of the extension in milliseconds.
	]
	```
""");
	XmlRpc::RegisterMethod(C_Method_WarmUp_Stop, """
* Name: {{{C_Method_WarmUp_Stop}}}
* Type: TriggerModeScriptEventArray
* Description: Stop any ongoing warmup.
* Data:
	- Version >=2.0.0:
	```
	[]
	```
""");
	XmlRpc::RegisterMethod(C_Method_WarmUp_BlockEndWarmUp, """
* Name: {{{C_Method_WarmUp_BlockEndWarmUp}}}
* Type: TriggerModeScriptEventArray
* Description: Disable the countdown when all teams' slots are filled and force all players to be ready before ending the warmup.
* Data:
	- Version >=2.0.0:
	```
	[
		"true", //< true to block the warmup countdown, false to unblock.
		"5000" //< Timer before the end of the warmup when all players are ready. Use a negative value to prevent the warmup from ending even if all players are ready.
	]
	```
""");
}