Class ExtPlayerReplicationInfo extends KFPlayerReplicationInfo;

struct FCustomCharEntry
{
	var bool bLock;
	var KFCharacterInfo_Human Char;
	var ObjectReferencer Ref;
};
struct FMyCustomChar // Now without constant.
{
	var int CharacterIndex,HeadMeshIndex,HeadSkinIndex,BodyMeshIndex,BodySkinIndex,AttachmentMeshIndices[`MAX_COSMETIC_ATTACHMENTS],AttachmentSkinIndices[`MAX_COSMETIC_ATTACHMENTS];
	
	structdefaultproperties
	{
		AttachmentMeshIndices[0]=`CLEARED_ATTACHMENT_INDEX
		AttachmentMeshIndices[1]=`CLEARED_ATTACHMENT_INDEX
		AttachmentMeshIndices[2]=`CLEARED_ATTACHMENT_INDEX
	}
};

// For custom trader inventory.
struct FCustomTraderItem
{
	var class<KFWeaponDefinition> WeaponDef;
	var class<KFWeapon> WeaponClass;
};

var bool bIsMuted,bInitialPT,bIsDev,bHiddenUser,bClientUseCustom,bClientFirstChar,bClientCharListDone,bClientInitChars;

var int RespawnCounter;
var byte AdminType;
var class<Ext_PerkBase> ECurrentPerk;
var Ext_PerkBase FCurrentPerk;
var int ECurrentPerkLevel,ECurrentPerkPrestige;
var ExtPerkManager PerkManager;
/* AdminTypes:
	0 - Super Admin (server owner)
	1 - Admin
	2 - Moderator
	3 - Trusted member
	4 - VIP
*/

var string TaggedPlayerName;
var repnotify string NameTag;
var repnotify byte RepLevelProgress;
var transient color HUDPerkColor;
var byte FixedData;
var int RepPlayTime,RepKills,RepEXP;

// Custom character stuff.
var array<FCustomCharEntry> CustomCharList;
var repnotify FMyCustomChar CustomCharacter;
var transient array<ExtCharDataInfo> SaveDataObjects;
var transient ExtPlayerReplicationInfo LocalOwnerPRI; // Local playercontroller owner PRI

// Custom trader inventory
var KFGFxObject_TraderItems CustomList;
var array<FCustomTraderItem> CustomItems;

// Supplier data:
var transient struct FSupplierData
{
	var transient Pawn SuppliedPawn;
	var transient float NextSupplyTimer;
} SupplierLimit;
var repnotify class<Ext_TraitSupply> HasSupplier;

replication
{
	// Things the server should send to the client.
	if (true)
		RespawnCounter,AdminType,ECurrentPerk,ECurrentPerkLevel,ECurrentPerkPrestige,RepKills,RepEXP,RepLevelProgress,bIsDev,NameTag,FixedData,bHiddenUser,CustomCharacter,HasSupplier;
	if (bNetInitial || bInitialPT)
		RepPlayTime;
}

simulated function PostBeginPlay()
{
	local PlayerController PC;

	Super.PostBeginPlay();
	SetTimer(1,true,'TickPT');
	if (WorldInfo.NetMode!=NM_DedicatedServer)
	{
		HUDPerkColor = PickPerkColor();
		PC = GetALocalPlayerController();
		if (PC!=None)
			LocalOwnerPRI = ExtPlayerReplicationInfo(PC.PlayerReplicationInfo);
	}
	else LocalOwnerPRI = Self; // Dedicated server can use self PRI.
}

// Resupply traits:
simulated final function bool CanUseSupply(Pawn P)
{
	return (SupplierLimit.SuppliedPawn!=P || SupplierLimit.NextSupplyTimer<WorldInfo.TimeSeconds);
}

simulated final function UsedSupply(Pawn P, float NextTime)
{
	SupplierLimit.SuppliedPawn = P;
	SupplierLimit.NextSupplyTimer = WorldInfo.TimeSeconds+NextTime;
}

simulated function ClientInitialize(Controller C)
{
	local ExtPlayerReplicationInfo PRI;

	Super.ClientInitialize(C);
	
	if (WorldInfo.NetMode!=NM_DedicatedServer)
	{
		LocalOwnerPRI = Self;

		// Make all other PRI's load character list from local owner PRI.
		foreach DynamicActors(class'ExtPlayerReplicationInfo',PRI)
			PRI.LocalOwnerPRI = Self;
	}
}

simulated function TickPT()
{
	++RepPlayTime;
}

simulated event ReplicatedEvent(name VarName)
{
	switch (VarName)
	{
	case 'RepLevelProgress':
		HUDPerkColor = PickPerkColor();
		break;
	case 'CustomCharacter':
		CharacterCustomizationChanged();
		break;
	case 'HasSupplier':
		SupplierLimit.SuppliedPawn = None; // Reset if stat was changed.
		break;
	case 'PlayerName':
	case 'NameTag':
		UpdateNameTag();
	default:
		Super.ReplicatedEvent(VarName);
	}
}

function SetPlayerName(string S)
{
	Super.SetPlayerName(S);
	UpdateNameTag();
}

function SetPlayerNameTag(string S)
{
	NameTag = S;
	UpdateNameTag();
}

function OverrideWith(PlayerReplicationInfo PRI)
{
	Super.OverrideWith(PRI);
	NameTag = ExtPlayerReplicationInfo(PRI).NameTag;
	bAdmin = PRI.bAdmin;
	AdminType = ExtPlayerReplicationInfo(PRI).AdminType;
	UpdateNameTag();
}

simulated final function UpdateNameTag()
{
	if (NameTag!="")
		TaggedPlayerName = "["$NameTag$"] "$PlayerName;
	else TaggedPlayerName = PlayerName;
}

final function SetLevelProgress(int CurLevel, int CurPrest, int MinLevel, int MaxLevel)
{
	local float V;

	ECurrentPerkLevel = CurLevel;
	ECurrentPerkPrestige = CurPrest;
	V = FClamp((float(CurLevel-MinLevel) / float(MaxLevel-MinLevel))*255.f,0,255);
	RepLevelProgress = V;
	bForceNetUpdate = true;

	if (WorldInfo.NetMode!=NM_DedicatedServer)
		HUDPerkColor = PickPerkColor();
}

simulated final function string GetPerkLevelStr()
{
	return (ECurrentPerkPrestige>0 ? (string(ECurrentPerkPrestige)$"-"$string(ECurrentPerkLevel)) : string(ECurrentPerkLevel));
}

simulated final function color PickPerkColor()
{
	local float P;
	local byte i;
	
	if (RepLevelProgress==0)
		return MakeColor(255,255,255,255);
	P = float(RepLevelProgress) / 255.f;
	if (P<0.25f) // White - Blue
	{
		i = 255 - (P*1020.f);
		return MakeColor(i,i,255,255);
	}
	if (P<0.5f) // Blue - Green
	{
		i = ((P-0.25f)*1020.f);
		return MakeColor(0,i,255-i,255);
	}
	if (P<0.75f) // Green - Red
	{
		i = ((P-0.5f)*1020.f);
		return MakeColor(i,255-i,0,255);
	}
	// Red - Yellow
	i = ((P-0.75f)*1020.f);
	return MakeColor(255,i,0,255);
}

function SetInitPlayTime(int T)
{
	bInitialPT = true;
	bForceNetUpdate = true;
	RepPlayTime = T;
	SetTimer(5,false,'UnsetPT');
}

function UnsetPT()
{
	bInitialPT = false;
}

Delegate bool OnRepNextItem(ExtPlayerReplicationInfo PRI, int RepIndex)
{
	return false;
}

simulated reliable client function ClientAddTraderItem(int Index, FCustomTraderItem Item)
{
	// Make sure to not execute on server.
	if (WorldInfo.NetMode!=NM_Client && (PlayerController(Owner)==None || LocalPlayer(PlayerController(Owner).Player)==None))
		return;

	if (CustomList==None)
	{
		CustomList = CreateNewList();
		RecheckGRI();
	}
	CustomItems.AddItem(Item);
	SetWeaponInfo(false,Index,Item,CustomList);
}

simulated static final function KFGFxObject_TraderItems CreateNewList()
{
	local KFGFxObject_TraderItems L;
	
	L = new class'KFGFxObject_TraderItems';
	L.SaleItems = L.Default.SaleItems;
	L.ArmorPrice = L.Default.ArmorPrice;
	L.GrenadePrice = L.Default.GrenadePrice;

	return L;
}

simulated static final function SetWeaponInfo(bool bDedicated, int Index, FCustomTraderItem Item, KFGFxObject_TraderItems List)
{
	local array<STraderItemWeaponStats> S;
	local int i;

	if (List.SaleItems.Length<=Index)
		List.SaleItems.Length = Index+1;

	List.SaleItems[Index].WeaponDef = Item.WeaponDef;
	List.SaleItems[Index].ClassName = Item.WeaponClass.Name;
	if (class<KFWeap_DualBase>(Item.WeaponClass)!=None && class<KFWeap_DualBase>(Item.WeaponClass).Default.SingleClass!=None)
		List.SaleItems[Index].SingleClassName = class<KFWeap_DualBase>(Item.WeaponClass).Default.SingleClass.Name;
	else List.SaleItems[Index].SingleClassName = '';
	List.SaleItems[Index].DualClassName = Item.WeaponClass.Default.DualClass!=None ? Item.WeaponClass.Default.DualClass.Name : '';
	List.SaleItems[Index].AssociatedPerkClasses = Item.WeaponClass.Static.GetAssociatedPerkClasses();
	List.SaleItems[Index].MaxSpareAmmo = Item.WeaponClass.Default.SpareAmmoCapacity[0];
	List.SaleItems[Index].MagazineCapacity = Item.WeaponClass.Default.MagazineCapacity[0];
	List.SaleItems[Index].InitialSpareMags = Item.WeaponClass.Default.InitialSpareMags[0];
	List.SaleItems[Index].MaxSecondaryAmmo = Item.WeaponClass.Default.MagazineCapacity[1] * Item.WeaponClass.Default.SpareAmmoCapacity[1];
	List.SaleItems[Index].BlocksRequired = Item.WeaponClass.Default.InventorySize;
	List.SaleItems[Index].ItemID = Index;

	List.SaleItems[Index].InitialSecondaryAmmo = Item.WeaponClass.Default.InitialSpareMags[1];
	List.SaleItems[Index].WeaponUpgradeDmgMultiplier[0] = 1.0;
	for (i = 0;i<Min(Item.WeaponClass.Default.WeaponUpgrades.Length, 5);i++)
	{
		List.SaleItems[Index].WeaponUpgradeWeight[i+1] = Item.WeaponClass.Static.GetUpgradeStatAdd(EWUS_Weight, i+1);
		List.SaleItems[Index].WeaponUpgradeDmgMultiplier[i+1] = Item.WeaponClass.Static.GetUpgradeStatScale(EWUS_Damage0, i+1);
	}

	if (!bDedicated)
	{
		List.SaleItems[Index].SecondaryAmmoImagePath = Item.WeaponClass.Default.SecondaryAmmoTexture!=None ? PathName(Item.WeaponClass.Default.SecondaryAmmoTexture) : "UI_SecondaryAmmo_TEX.GasTank";
		List.SaleItems[Index].TraderFilter = Item.WeaponClass.Static.GetTraderFilter();
		List.SaleItems[Index].InventoryGroup = Item.WeaponClass.Default.InventoryGroup;
		List.SaleItems[Index].GroupPriority = Item.WeaponClass.Default.GroupPriority;
		Item.WeaponClass.Static.SetTraderWeaponStats(S);
		List.SaleItems[Index].WeaponStats = S;
	}
}

simulated function RecheckGRI()
{
	local ExtPlayerController PC;

	if (KFGameReplicationInfo(WorldInfo.GRI)==None)
		SetTimer(0.1,false,'RecheckGRI');
	else
	{
		KFGameReplicationInfo(WorldInfo.GRI).TraderItems = CustomList;
		foreach LocalPlayerControllers(class'ExtPlayerController',PC)
		{
			if (PC.PurchaseHelper!=None)
			{
				PC.PurchaseHelper.TraderItems = CustomList;
			}
		}
	}
}

simulated final function bool ShowAdminName()
{
	return (bAdmin || AdminType<255);
}

simulated function string GetAdminName()
{
	switch (AdminType)
	{
	case 0:
		return "Super Admin";
	case 1:
	case 255:
		return "Admin";
	case 2:
		return "Mod";
	case 3:
		return "Trusted Member";
	case 4:
		return "VIP";
	}
}

simulated function string GetAdminNameAbr()
{
	switch (AdminType)
	{
	case 0:
		return "S";
	case 1:
	case 255:
		return "A";
	case 2:
		return "M";
	case 3:
		return "T";
	case 4:
		return "V";
	}
}

simulated function string GetAdminColor()
{
	switch (AdminType)
	{
	case 0:
		return "FF6600";
	case 1:
	case 255:
		return "40FFFF";
	case 2:
		return "FF33FF";
	case 3:
		return "FF0000";
	case 4:
		return "FFD700";
	}
}

simulated function color GetAdminColorC()
{
	switch (AdminType)
	{
	case 0:
		return MakeColor(255,102,0,255);
	case 1:
	case 255:
		return MakeColor(64,255,255,255);
	case 2:
		return MakeColor(255,51,255,255);
	case 3:
		return MakeColor(255,0,0,255);
	case 4:
		return MakeColor(255,215,0,255);
	}
}

simulated function string GetHumanReadableName()
{
	return TaggedPlayerName;
}

function SetFixedData(byte M)
{
	OnModeSet(Self,M);
	FixedData = FixedData | M;
	SetTimer(5,false,'ClearFixed');
}

function ClearFixed()
{
	FixedData = 0;
}

simulated final function string GetDesc()
{
	local string S;
	
	if ((FixedData & 1)!=0)
		S = "A.";
	if ((FixedData & 2)!=0)
		S $= "WF.";
	if ((FixedData & 4)!=0)
		S $= "G.";
	if ((FixedData & 8)!=0)
		S $= "NW.";
	if ((FixedData & 16)!=0)
		S $= "WA.";
	return S;
}

delegate OnModeSet(ExtPlayerReplicationInfo PRI, byte Num);

simulated final function bool LoadPlayerCharacter(byte CharIndex, out FMyCustomChar CharInfo)
{
	local KFCharacterInfo_Human C;

	if (CharIndex>=(CharacterArchetypes.Length+CustomCharList.Length))
		return false;

	if (SaveDataObjects.Length<=CharIndex)
		SaveDataObjects.Length = CharIndex+1;
	if (SaveDataObjects[CharIndex]==None)
	{
		C = (CharIndex<CharacterArchetypes.Length) ? CharacterArchetypes[CharIndex] : CustomCharList[CharIndex-CharacterArchetypes.Length].Char;
		SaveDataObjects[CharIndex] = new(None,PathName(C)) class'ExtCharDataInfo';
	}
	CharInfo = SaveDataObjects[CharIndex].LoadData();
	return true;
}

simulated final function bool SavePlayerCharacter()
{
	local KFCharacterInfo_Human C;

	if (CustomCharacter.CharacterIndex>=(CharacterArchetypes.Length+CustomCharList.Length))
		return false;

	if (SaveDataObjects.Length<=CustomCharacter.CharacterIndex)
		SaveDataObjects.Length = CustomCharacter.CharacterIndex+1;
	if (SaveDataObjects[CustomCharacter.CharacterIndex]==None)
	{
		C = (CustomCharacter.CharacterIndex<CharacterArchetypes.Length) ? CharacterArchetypes[CustomCharacter.CharacterIndex] : CustomCharList[CustomCharacter.CharacterIndex-CharacterArchetypes.Length].Char;
		SaveDataObjects[CustomCharacter.CharacterIndex] = new(None,PathName(C)) class'ExtCharDataInfo';
	}
	SaveDataObjects[CustomCharacter.CharacterIndex].SaveData(CustomCharacter);
	return true;
}

simulated function ChangeCharacter(byte CharIndex, optional bool bFirstSet)
{
	local FMyCustomChar NewChar;
	local byte i;

	if (CharIndex>=(CharacterArchetypes.Length+CustomCharList.Length) || IsClientCharLocked(CharIndex))
		CharIndex = 0;

	if (bFirstSet && RepCustomizationInfo.CharacterIndex==CharIndex)
	{
		// Copy properties from default character info.
		NewChar.HeadMeshIndex = RepCustomizationInfo.HeadMeshIndex;
		NewChar.HeadSkinIndex = RepCustomizationInfo.HeadSkinIndex;
		NewChar.BodyMeshIndex = RepCustomizationInfo.BodyMeshIndex;
		NewChar.BodySkinIndex = RepCustomizationInfo.BodySkinIndex;
		for (i=0; i<`MAX_COSMETIC_ATTACHMENTS; ++i)
		{
			NewChar.AttachmentMeshIndices[i] = RepCustomizationInfo.AttachmentMeshIndices[i];
			NewChar.AttachmentSkinIndices[i] = RepCustomizationInfo.AttachmentSkinIndices[i];
		}
	}
	if (LoadPlayerCharacter(CharIndex,NewChar))
	{
		NewChar.CharacterIndex = CharIndex;
		CustomCharacter = NewChar;
		ServerSetCharacterX(NewChar);
		if (WorldInfo.NetMode==NM_Client)
			CharacterCustomizationChanged();
	}
}

simulated function UpdateCustomization(int Type, int MeshIndex, int SkinIndex, optional int SlotIndex)
{
	switch (Type)
	{
	case CO_Head:
		CustomCharacter.HeadMeshIndex = MeshIndex;
		CustomCharacter.HeadSkinIndex = SkinIndex;
		break;
	case CO_Body:
		CustomCharacter.BodyMeshIndex = MeshIndex;
		CustomCharacter.BodySkinIndex = SkinIndex;
		break;
	case CO_Attachment:
		CustomCharacter.AttachmentMeshIndices[SlotIndex] = MeshIndex;
		CustomCharacter.AttachmentSkinIndices[SlotIndex] = SkinIndex;
		break;
	}
	SavePlayerCharacter();
	ServerSetCharacterX(CustomCharacter);
	if (WorldInfo.NetMode==NM_Client)
		CharacterCustomizationChanged();
}

simulated final function RemoveAttachments()
{
	local byte i;

	for (i=0; i<`MAX_COSMETIC_ATTACHMENTS; ++i)
	{
		CustomCharacter.AttachmentMeshIndices[i] = `CLEARED_ATTACHMENT_INDEX;
		CustomCharacter.AttachmentSkinIndices[i] = 0;
	}
	SavePlayerCharacter();
	ServerSetCharacterX(CustomCharacter);
	if (WorldInfo.NetMode==NM_Client)
		CharacterCustomizationChanged();
}

simulated function ClearCharacterAttachment(int AttachmentIndex)
{
	if (UsesCustomChar())
	{
		CustomCharacter.AttachmentMeshIndices[AttachmentIndex] = `CLEARED_ATTACHMENT_INDEX;
		CustomCharacter.AttachmentSkinIndices[AttachmentIndex] = 0;
	}
	else Super.ClearCharacterAttachment(AttachmentIndex);
}

reliable server final function ServerSetCharacterX(FMyCustomChar NewMeshInfo)
{
	if (NewMeshInfo.CharacterIndex>=(CharacterArchetypes.Length+CustomCharList.Length) || IsClientCharLocked(NewMeshInfo.CharacterIndex))
		return;

	CustomCharacter = NewMeshInfo;

	if (Role == Role_Authority)
	{
		CharacterCustomizationChanged();
	}
}

simulated final function bool IsClientCharLocked(byte Index)
{
	if (Index<CharacterArchetypes.Length)
		return false;
	Index-=CharacterArchetypes.Length;
	return (Index<CustomCharList.Length && CustomCharList[Index].bLock && !ShowAdminName());
}

simulated reliable client function ReceivedCharacter(byte Index, FCustomCharEntry C)
{
	if (WorldInfo.NetMode==NM_DedicatedServer)
		return;

	if (CustomCharList.Length<=Index)
		CustomCharList.Length = Index+1;
	CustomCharList[Index] = C;
}

simulated reliable client function AllCharReceived()
{
	if (WorldInfo.NetMode==NM_DedicatedServer)
		return;

	if (!bClientInitChars)
	{
		OnCharListDone();
		NotifyCharListDone();
		bClientInitChars = true;
	}
}

simulated final function NotifyCharListDone()
{
	local KFPawn_Human KFP;
	local KFCharacterInfo_Human NewCharArch;
	local ExtPlayerReplicationInfo EPRI;

	foreach WorldInfo.AllPawns(class'KFPawn_Human', KFP)
	{
		EPRI = ExtPlayerReplicationInfo(KFP.PlayerReplicationInfo);
		if (EPRI!=None)
		{
			NewCharArch = EPRI.GetSelectedArch();

			if (NewCharArch != KFP.CharacterArch)
			{
				// selected a new character
				KFP.SetCharacterArch(NewCharArch);
			}
			else if (WorldInfo.NetMode != NM_DedicatedServer)
			{
				// refresh cosmetics only
				class'ExtCharacterInfo'.Static.SetCharacterMeshFromArch(NewCharArch, KFP, EPRI);
			}
		}
	}
}

simulated delegate OnCharListDone();

// Player has a server specific setting for a character selected.
simulated final function bool UsesCustomChar()
{
	if (LocalOwnerPRI==None)
		return false; // Not yet init on client.
	return CustomCharacter.CharacterIndex<(LocalOwnerPRI.CustomCharList.Length+CharacterArchetypes.Length);
}

// Client uses a server specific custom character.
simulated final function bool ReallyUsingCustomChar()
{
	if (!UsesCustomChar())
		return false;
	return (CustomCharacter.CharacterIndex>=CharacterArchetypes.Length);
}

simulated final function KFCharacterInfo_Human GetSelectedArch()
{
	if (UsesCustomChar())
		return (CustomCharacter.CharacterIndex<CharacterArchetypes.Length) ? CharacterArchetypes[CustomCharacter.CharacterIndex] : LocalOwnerPRI.CustomCharList[CustomCharacter.CharacterIndex-CharacterArchetypes.Length].Char;
	return CharacterArchetypes[RepCustomizationInfo.CharacterIndex];
}

simulated event CharacterCustomizationChanged()
{
	local KFPawn_Human KFP;
	local KFCharacterInfo_Human NewCharArch;

	foreach WorldInfo.AllPawns(class'KFPawn_Human', KFP)
	{
		if (KFP.PlayerReplicationInfo == self || (KFP.DrivenVehicle != None && KFP.DrivenVehicle.PlayerReplicationInfo == self))
		{
			NewCharArch = GetSelectedArch();

			if (NewCharArch != KFP.CharacterArch)
			{
				// selected a new character
				KFP.SetCharacterArch(NewCharArch);
			}
			else if (WorldInfo.NetMode != NM_DedicatedServer)
			{
				// refresh cosmetics only
				class'ExtCharacterInfo'.Static.SetCharacterMeshFromArch(NewCharArch, KFP, self);
			}
		}
	}
}

// Save/Load custom character information.
final function SaveCustomCharacter(ExtSaveDataBase Data)
{
	local byte i,c;
	local string S;

	// Write the name of custom character.
	if (UsesCustomChar())
		S = string(GetSelectedArch().Name);
	Data.SaveStr(S);
	if (S=="")
		return;
	
	// Write selected accessories.
	Data.SaveInt(CustomCharacter.HeadMeshIndex);
	Data.SaveInt(CustomCharacter.HeadSkinIndex);
	Data.SaveInt(CustomCharacter.BodyMeshIndex);
	Data.SaveInt(CustomCharacter.BodySkinIndex);
	
	c = 0;
	for (i=0; i<`MAX_COSMETIC_ATTACHMENTS; ++i)
	{
		if (CustomCharacter.AttachmentMeshIndices[i]!=`CLEARED_ATTACHMENT_INDEX)
			++c;
	}

	// Write attachments count.
	Data.SaveInt(c);
	
	// Write attachments.
	for (i=0; i<`MAX_COSMETIC_ATTACHMENTS; ++i)
	{
		if (CustomCharacter.AttachmentMeshIndices[i]!=`CLEARED_ATTACHMENT_INDEX)
		{
			Data.SaveInt(i);
			Data.SaveInt(CustomCharacter.AttachmentMeshIndices[i]);
			Data.SaveInt(CustomCharacter.AttachmentSkinIndices[i]);
		}
	}
}

final function LoadCustomCharacter(ExtSaveDataBase Data)
{
	local string S;
	local byte i,n,j;

	if (Data.GetArVer()>=2)
		S = Data.ReadStr();
	if (S=="") // Stock skin.
		return;

	for (i=0; i<CharacterArchetypes.Length; ++i)
	{
		if (string(CharacterArchetypes[i].Name)~=S)
			break;
	}
	
	if (i==CharacterArchetypes.Length)
	{
		for (i=0; i<CustomCharList.Length; ++i)
		{
			if (string(CustomCharList[i].Char.Name)~=S)
				break;
		}
		if (i==CharacterArchetypes.Length)
		{
			// Character not found = Skip data.
			Data.SkipBytes(4);
			n = Data.ReadInt();
			for (i=0; i<n; ++i)
				Data.SkipBytes(3);
			return;
		}
		i+=CharacterArchetypes.Length;
	}

	CustomCharacter.CharacterIndex = i;
	CustomCharacter.HeadMeshIndex = Data.ReadInt();
	CustomCharacter.HeadSkinIndex = Data.ReadInt();
	CustomCharacter.BodyMeshIndex = Data.ReadInt();
	CustomCharacter.BodySkinIndex = Data.ReadInt();

	n = Data.ReadInt();
	for (i=0; i<n; ++i)
	{
		j = Min(Data.ReadInt(),`MAX_COSMETIC_ATTACHMENTS-1);
		CustomCharacter.AttachmentMeshIndices[j] = Data.ReadInt();
		CustomCharacter.AttachmentSkinIndices[j] = Data.ReadInt();
	}
	bNetDirty = true;
}

// Only used to skip offset (in case of an error).
static final function DummyLoadChar(ExtSaveDataBase Data)
{
	local string S;
	local byte i,n;

	if (Data.GetArVer()>=2)
		S = Data.ReadStr();
	if (S=="") // Stock skin.
		return;

	Data.SkipBytes(4);
	n = Data.ReadInt();
	for (i=0; i<n; ++i)
		Data.SkipBytes(3);
}

static final function DummySaveChar(ExtSaveDataBase Data)
{
	Data.SaveStr("");
}

simulated function Texture2D GetCurrentIconToDisplay()
{
	if (CurrentVoiceCommsRequest == VCT_NONE && ECurrentPerk != none)
	{
		return ECurrentPerk.default.PerkIcon;
	}

	return class'KFLocalMessage_VoiceComms'.default.VoiceCommsIcons[CurrentVoiceCommsRequest];
}

// Set admin levels without having to hard-reference to this mod.
event BeginState(Name N)
{
	switch (N)
	{
	case 'Global':
		AdminType = 0;
		break;
	case 'Admin':
		AdminType = 1;
		break;
	case 'Mod':
		AdminType = 2;
		break;
	case 'TMem':
		AdminType = 3;
		break;
	case 'VIP':
		AdminType = 4;
		break;
	case 'User':
		AdminType = 255;
		break;
	}
}

defaultproperties
{
	RespawnCounter=-1
	AdminType=255
	TaggedPlayerName="Player"
}