Class ExtHumanPawn extends KFPawn_Human;

// Forrests backpack weapon and first person legs.
var SkeletalMeshComponent AttachedBackItem;
var SkeletalMeshComponent FPBodyMesh;
var repnotify class<KFWeapon> BackpackWeaponClass;
var KFWeapon PlayerOldWeapon;

var transient float NextRedeemTimer,BHopSpeedMod;
var float KnockbackResist,NoRagdollChance;
var AnimSet WakeUpAnimSet;
var name FeignRecoverAnim;
var byte UnfeignFailedCount,RepRegenHP,BHopAccelSpeed;
var repnotify bool bFeigningDeath;
var bool bPlayingFeignDeathRecovery,bRagdollFromFalling,bRagdollFromBackhit,bRagdollFromMomentum,bCanBecomeRagdoll,bRedeadMode,bPendingRedead,bHasBunnyHop,bOnFirstPerson,bFPLegsAttached,bFPLegsInit;

var byte HealingShieldMod,HealingSpeedBoostMod,HealingDamageBoostMod;

replication
{
	if (true)
		bFeigningDeath,RepRegenHP,BackpackWeaponClass;
	if (bNetOwner)
		bHasBunnyHop;
	if (bNetDirty)
		HealingSpeedBoostMod, HealingDamageBoostMod, HealingShieldMod;
}

function TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
	if (KnockbackResist<1)
		Momentum *= KnockbackResist;
	Super.TakeDamage(Damage,InstigatedBy,HitLocation,Momentum,DamageType,HitInfo,DamageCauser);
}

simulated function bool Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
	local ExtPlayerController C;
	local class<Pawn> KillerPawn;
	local PlayerReplicationInfo KillerPRI;
	local SeqAct_Latent Action;

	if (WorldInfo.NetMode!=NM_Client && PlayerReplicationInfo!=None)
	{
		if (Killer==None || Killer==Controller)
		{
			KillerPRI = PlayerReplicationInfo;
			KillerPawn = None;
		}
		else
		{
			KillerPRI = Killer.PlayerReplicationInfo;
			if (KillerPRI==None || KillerPRI.Team!=PlayerReplicationInfo.Team)
			{
				KillerPawn = Killer.Pawn!=None ? Killer.Pawn.Class : None;
				if (PlayerController(Killer)==None) // If was killed by a monster, don't broadcast PRI along with it.
					KillerPRI = None;
			}
			else KillerPawn = None;
		}
		foreach WorldInfo.AllControllers(class'ExtPlayerController',C)
			C.ClientKillMessage(damageType,PlayerReplicationInfo,KillerPRI,KillerPawn);
	}
	// If got killed by a zombie, turn player into a ragdoll and let em take control of a newly spawned ZED over the ragdoll.
	if (bRedeadMode && WorldInfo.NetMode!=NM_Client && damageType!=None && Killer!=None && Killer!=Controller && Killer.GetTeamNum()!=0)
	{
		if (bDeleteMe || WorldInfo.Game == None || WorldInfo.Game.bLevelChange)
			return FALSE;
		bPendingRedead = true;
		if (WorldInfo.Game.PreventDeath(self, Killer, damageType, HitLocation))
		{
			bPendingRedead = false;
			Health = max(Health, 1);
			return false;
		}
		Health = 0;
		foreach LatentActions(Action)
			Action.Abortfor (self);
		if (Controller != None)
			WorldInfo.Game.Killed(Killer, Controller, self, damageType);
		else WorldInfo.Game.Killed(Killer, Controller(Owner), self, damageType);
		
		if (InvManager != None)
			InvManager.OwnerDied();
		
		Health = 1;
		if (!bFeigningDeath)
			PlayFeignDeath(true,,true);
		Health = 0;
		ClearTimer('UnsetFeignDeath');
		GoToState('TransformZed');
		return true;
	}
	return Super.Died(Killer, DamageType, HitLocation);
}

simulated function BroadcastDeathMessage(Controller Killer);

function SetBatteryRate(float Rate)
{
	BatteryDrainRate = Default.BatteryDrainRate*Rate;
	NVGBatteryDrainRate = Default.NVGBatteryDrainRate*Rate;
	ClientSetBatteryRate(Rate);
}

simulated reliable client function ClientSetBatteryRate(float Rate)
{
	BatteryDrainRate = Default.BatteryDrainRate*Rate;
	NVGBatteryDrainRate = Default.NVGBatteryDrainRate*Rate;
}

event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType, optional bool bCanRepairArmor=true, optional bool bMessageHealer=true)
{
	local int DoshEarned,UsedHealAmount;
	local float ScAmount;
	local KFPlayerReplicationInfo InstigatorPRI;
	local ExtPlayerController InstigatorPC, KFPC;
	local KFPerk InstigatorPerk;
	local class<KFDamageType> KFDT;
	local int i;
	local bool bRepairedArmor;
	local ExtPlayerReplicationInfo EPRI;
	local Ext_PerkBase InstigatorExtPerk;

	InstigatorPC = ExtPlayerController(Healer);
	InstigatorPerk = InstigatorPC.GetPerk();
	
	if (InstigatorPerk != None && bCanRepairArmor)
		bRepairedArmor = InstigatorPC.GetPerk().RepairArmor(self);
	
	EPRI = ExtPlayerReplicationInfo(InstigatorPC.PlayerReplicationInfo);
	if (EPRI != none)
	{
		InstigatorExtPerk = ExtPlayerController(Controller).ActivePerkManager.CurrentPerk;
		if (InstigatorExtPerk != none && Ext_PerkFieldMedic(InstigatorExtPerk) != none)
		{
			if (Ext_PerkFieldMedic(InstigatorExtPerk).bHealingBoost)
				UpdateHealingSpeedBoostMod(InstigatorPC);

			if (Ext_PerkFieldMedic(InstigatorExtPerk).bHealingDamageBoost)
				UpdateHealingDamageBoostMod(InstigatorPC);

			if (Ext_PerkFieldMedic(InstigatorExtPerk).bHealingShield)
				UpdateHealingShieldMod(InstigatorPC);
		}
	}

	if (Amount > 0 && IsAliveAndWell() && Health < HealthMax)
	{
		// Play any healing effects attached to this damage type
		KFDT = class<KFDamageType>(DamageType);
		if (KFDT != none && KFDT.default.bNoPain)
			PlayHeal(KFDT);

		if (Role == ROLE_Authority)
		{
			if (Healer==None || Healer.PlayerReplicationInfo == None)
				return false;

			InstigatorPRI = KFPlayerReplicationInfo(Healer.PlayerReplicationInfo);
			ScAmount = Amount;
			if (InstigatorPerk != none)
				InstigatorPerk.ModifyHealAmount(ScAmount);
			UsedHealAmount = ScAmount;

			// You can never have a HealthToRegen value that's greater than HealthMax
			if (Health + HealthToRegen + UsedHealAmount > HealthMax)
				UsedHealAmount = Min(HealthMax - (Health + HealthToRegen),255-HealthToRegen);
			else UsedHealAmount = Min(UsedHealAmount,255-HealthToRegen);

			HealthToRegen += UsedHealAmount;
			RepRegenHP = HealthToRegen;
			if (!IsTimerActive('GiveHealthOverTime'))
				SetTimer(HealthRegenRate, true, 'GiveHealthOverTime');

			// Give the healer money/XP for helping a teammate
			if (Healer.Pawn != none && Healer.Pawn != self)
			{
				DoshEarned = (UsedHealAmount / float(HealthMax)) * HealerRewardScaler;
				if (InstigatorPRI!=None)
					InstigatorPRI.AddDosh(Max(DoshEarned, 0), true);
				if (InstigatorPC!=None)
					InstigatorPC.AddHealPoints(UsedHealAmount);
			}

			if (Healer.bIsPlayer)
			{
				if (Healer != Controller)
				{
					if (InstigatorPC!=None)
					{
						if (!InstigatorPC.bClientHideNumbers)
							InstigatorPC.ClientNumberMsg(UsedHealAmount,Location,DMG_Heal);
						InstigatorPC.ReceiveLocalizedMessage(class'KFLocalMessage_Game', GMT_HealedPlayer, PlayerReplicationInfo);
					}
					KFPC = ExtPlayerController(Controller);
					if (KFPC!=None)
						KFPC.ReceiveLocalizedMessage(class'KFLocalMessage_Game', GMT_HealedBy, Healer.PlayerReplicationInfo);
				}
				else if (bMessageHealer && InstigatorPC!=None)
					InstigatorPC.ReceiveLocalizedMessage(class'KFLocalMessage_Game', GMT_HealedSelf, PlayerReplicationInfo);
			}

			// don't play dialog for healing done through perk skills (e.g. berserker vampire skill)
			if (bMessageHealer)
			{
				`DialogManager.PlayHealingDialog(KFPawn(Healer.Pawn), self, float(Health + HealthToRegen) / float(HealthMax));
			}

			// Reduce burn duration and damage in half if you heal while burning
			for (i = 0; i < DamageOverTimeArray.Length; ++i)
			{
				if (DamageOverTimeArray[i].DoT_Type == DOT_Fire)
				{
					DamageOverTimeArray[i].Duration *= 0.5;
					DamageOverTimeArray[i].Damage *= 0.5;
					break;
				}
			}

			return true;
		}
	}

	return bRepairedArmor;
}

function GiveHealthOverTime()
{
	Super.GiveHealthOverTime();
	RepRegenHP = HealthToRegen;
}

simulated event ReplicatedEvent(name VarName)
{
	switch (VarName)
	{
	case 'bFeigningDeath':
		PlayFeignDeath(bFeigningDeath);
		break;
	case 'BackpackWeaponClass':
		SetBackpackWeapon(BackpackWeaponClass);
		break;
	default:
		Super.ReplicatedEvent(VarName);
	}
}

// ==================================================================
// Feign death triggers:
function PlayHit(float Damage, Controller InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, TraceHitInfo HitInfo)
{
	if (damageType!=class'DmgType_Fell') // Not from falling!
	{
		if (bRagdollFromMomentum && Damage>2 && VSizeSq(Momentum)>1000000.f && Rand(3)==0) // Square(1000)
			SetFeignDeath(3.f+FRand()*2.5f); // Randomly knockout a player if hit by a huge force.
		else if (bRagdollFromBackhit && Damage>20 && VSizeSq(Momentum)>40000.f && (vector(Rotation) Dot Momentum)>0.f && Rand(4)==0)
			SetFeignDeath(2.f+FRand()*3.f); // Randomly knockout a player if hit from behind.
	}
	Super.PlayHit(Damage,InstigatedBy,HitLocation,damageType,Momentum,HitInfo);
}

event Landed(vector HitNormal, actor FloorActor)
{
	local float ExcessSpeed;

	Super.Landed(HitNormal, FloorActor);
	if (bRagdollFromFalling)
	{
		ExcessSpeed = Velocity.Z / (-MaxFallSpeed);
		if (ExcessSpeed>1.25) // Knockout a player after landed from too high.
		{
			Velocity.Z = 0; // Dont go clip through floor now...
			Velocity.X*=0.5;
			Velocity.Y*=0.5;
			SetFeignDeath((3.f+FRand())*ExcessSpeed);
		}
	}
	else if (BHopAccelSpeed>0)
		SetTimer((IsLocallyControlled() ? 0.17 : 1.f),false,'ResetBHopAccel'); // Replicating from client to server here because Server Tickrate may screw clients over from executing bunny hopping.
}

// ==================================================================
// Bunny hopping:
function bool DoJump(bool bUpdating)
{
	local float V;

	if (Super.DoJump(bUpdating))
	{
		// Accelerate if bunnyhopping.
		if (bHasBunnyHop && VSizeSq2D(Velocity)>Square(GroundSpeed*0.75))
		{
			if (BHopAccelSpeed<20)
			{
				if (BHopAccelSpeed==0)
					BHopSpeedMod = 1.f;

				if (BHopAccelSpeed<5)
					V = 1.15;
				else
				{
					V = 1.05;
					AirControl = 0.8;
				}
				BHopSpeedMod *= V;
				GroundSpeed *= V;
				SprintSpeed *= V;
				Velocity.X *= V;
				Velocity.Y *= V;
				++BHopAccelSpeed;
			}
			ClearTimer('ResetBHopAccel');
		}
		return true;
	}
	return false;
}

simulated function ResetBHopAccel(optional bool bSkipRep) // Set on Landed, or Tick if falling 2D speed is too low.
{
	if (BHopAccelSpeed>0)
	{
		BHopAccelSpeed = 0;
		AirControl = Default.AirControl;
		GroundSpeed /= BHopSpeedMod;
		UpdateGroundSpeed();
		if (WorldInfo.NetMode==NM_Client && !bSkipRep)
			NotifyHasStopped();
	}
}

function UpdateGroundSpeed()
{
	local KFInventoryManager InvM;
	local float HealthMod;

	if (Role < ROLE_Authority)
		return;

	InvM = KFInventoryManager(InvManager);
	HealthMod = (InvM != None) ? InvM.GetEncumbranceSpeedMod() : 1.f * (1.f - LowHealthSpeedPenalty);
	if (BHopAccelSpeed>0)
		HealthMod *= BHopSpeedMod;

	// First reset to default so multipliers do not stack
	GroundSpeed = default.GroundSpeed * HealthMod;
	// reset sprint too, because perk may want to scale it
	SprintSpeed = default.SprintSpeed * HealthMod;

	// Ask our perk to set the new ground speed based on weapon type
	if (GetPerk() != none)
	{
		GetPerk().ModifySpeed(GroundSpeed);
		GetPerk().ModifySpeed(SprintSpeed);
	}
}

reliable server function NotifyHasStopped()
{
	ResetBHopAccel(true);
}

// ==================================================================
// Feign death (UT3):
simulated function Tick(float Delta)
{
	Super.Tick(Delta);
	if (bPlayingFeignDeathRecovery)
	{
		// interpolate Controller yaw to our yaw so that we don't get our rotation snapped around when we get out of feign death
		Mesh.PhysicsWeight = FMax(Mesh.PhysicsWeight-(Delta*2.f),0.f);
		if (Mesh.PhysicsWeight<=0)
			StartFeignDeathRecoveryAnim();
	}
	if (BHopAccelSpeed>0)
	{
		if (Physics==PHYS_Falling && VSizeSq2D(Velocity)<Square(GroundSpeed*0.7))
			ResetBHopAccel(true);
	}
	if (WorldInfo.NetMode!=NM_Client && BackpackWeaponClass!=none && (PlayerOldWeapon==None || PlayerOldWeapon.Instigator==None))
	{
		PlayerOldWeapon = None;
		SetBackpackWeapon(None);
	}
}

function DelayedRagdoll()
{
	SetFeignDeath(2.f+FRand()*3.f);
}

exec function FeignDeath(float Time)
{
	SetFeignDeath(Time);
}

function SetFeignDeath(float Time)
{
	if (WorldInfo.NetMode!=NM_Client && !bFeigningDeath && Health>0 && bCanBecomeRagdoll && NoRagdollChance<1.f && (NoRagdollChance==0.f || FRand()>NoRagdollChance))
	{
		Time = FMax(1.f,Time);
		PlayFeignDeath(true);
		SetTimer(Time,false,'UnsetFeignDeath');
	}
}

function UnsetFeignDeath()
{
	if (bFeigningDeath)
		PlayFeignDeath(false);
}

simulated function PlayFeignDeath(bool bEnable, optional bool bForce, optional bool bTransformMode)
{
	local vector FeignLocation, HitLocation, HitNormal, TraceEnd, Impulse;
	local rotator NewRotation;
	local float UnFeignZAdjust;

	if (Health<=0 && WorldInfo.NetMode!=NM_Client)
		return; // If dead, don't do it.

	NotifyOutOfBattery(); // Stop nightvision on client.

	bFeigningDeath = bEnable;
	if (bEnable)
	{
		if (bFPLegsAttached)
		{
			bFPLegsAttached = false;
			DetachComponent(FPBodyMesh);
		}
		WeaponAttachmentTemplate = None;
		WeaponAttachmentChanged();
		
		bPlayingFeignDeathRecovery = false;
		ClearTimer('OnWakeUpFinished');
		if (!bTransformMode)
			GotoState('FeigningDeath');

		// if we had some other rigid body thing going on, cancel it
		if (Physics == PHYS_RigidBody)
		{
			//@note: Falling instead of None so Velocity/Acceleration don't get cleared
			setPhysics(PHYS_Falling);
		}

		PrepareRagdoll();

		SetPawnRBChannels(TRUE);
		Mesh.ForceSkelUpdate();

		// Move into post so that we are hitting physics from last frame, rather than animated from this
		SetTickGroup(TG_PostAsyncWork);
		
		// Turn collision on for skelmeshcomp and off for cylinder
		CylinderComponent.SetActorCollision(false, false);
		Mesh.SetActorCollision(true, true);
		Mesh.SetTraceBlocking(true, true);

		Mesh.SetHasPhysicsAssetInstance(false);
		
		if (!InitRagdoll()) // Ragdoll error!
		{
			if (PlayerController(Controller)!=None)
				PlayerController(Controller).ClientMessage("Error: InitRagdoll() failed!");
			return;
		}
		
		// Ensure we are always updating kinematic
		Mesh.MinDistFactorForKinematicUpdate = 0.0;

		Mesh.bUpdateKinematicBonesFromAnimation=FALSE;

		// Set all kinematic bodies to the current root velocity, since they may not have been updated during normal animation
		// and therefore have zero derived velocity (this happens in 1st person camera mode).
		UnFeignZAdjust = VSize(Velocity);
		if (UnFeignZAdjust>700.f) // Limit by a maximum velocity force to prevent from going through walls.
			Mesh.SetRBLinearVelocity((Velocity/UnFeignZAdjust)*700.f, false);
		else Mesh.SetRBLinearVelocity(Velocity, false);

		// reset mesh translation since adjustment code isn't executed on the server
		// but the ragdoll code uses the translation so we need them to match up for the
		// most accurate simulation
		Mesh.SetTranslation(vect(0,0,1) * BaseTranslationOffset);
		// we'll use the rigid body collision to check for falling damage
		Mesh.ScriptRigidBodyCollisionThreshold = MaxFallSpeed;
		Mesh.SetNotifyRigidBodyCollision(true);
	}
	else
	{
		// fit cylinder collision into location, crouching if necessary
		FeignLocation = Location;
		CollisionComponent = CylinderComponent;
		TraceEnd = Location + vect(0,0,1) * GetCollisionHeight();
		if (Trace(HitLocation, HitNormal, TraceEnd, Location, true, GetCollisionExtent()) == None)
		{
			HitLocation = TraceEnd;
		}
		if (!SetFeignEndLocation(HitLocation, FeignLocation) && WorldInfo.NetMode!=NM_Client)
		{
			UnfeignFailedCount++;
			if (UnFeignfailedCount > 4 || bForce)
			{
				SetLocation(PickNearestNode()); // Just teleport to nearest pathnode.
			}
			else
			{
				CollisionComponent = Mesh;
				SetLocation(FeignLocation);
				bFeigningDeath = true;
				Impulse = VRand();
				Impulse.Z = 0.5;
				Mesh.AddImpulse(800.0*Impulse, Location);
				SetTimer(1.f,false,'UnsetFeignDeath');
				return;
			}
		}
		
		PreRagdollCollisionComponent = None;

		// Calculate how far we just moved the actor up.
		UnFeignZAdjust = Location.Z - FeignLocation.Z;
		// If its positive, move back down by that amount until it hits the floor
		if (UnFeignZAdjust > 0.0)
		{
			moveSmooth(vect(0,0,-1) * UnFeignZAdjust);
		}

		UnfeignFailedCount = 0;

		bPlayingFeignDeathRecovery = true;
		
		// Reset collision.
		Mesh.SetActorCollision(true, false);
		Mesh.SetTraceBlocking(true, false);

		SetTickGroup(TG_PreAsyncWork);

		// don't need collision events anymore
		Mesh.SetNotifyRigidBodyCollision(false);

		// don't allow player to move while animation is in progress
		SetPhysics(PHYS_None);

		// physics weight interpolated to 0 in C++, then StartFeignDeathRecoveryAnim() is called
		Mesh.PhysicsWeight = 1.0;
		
		// force rotation to match the body's direction so the blend to the getup animation looks more natural
		NewRotation = Rotation;
		NewRotation.Yaw = rotator(Mesh.GetBoneAxis(HeadBoneName, AXIS_X)).Yaw;
		// flip it around if the head is facing upwards, since the animation for that makes the character
		// end up facing in the opposite direction that its body is pointing on the ground
		// FIXME: generalize this somehow (stick it in the AnimNode, I guess...)
		if (Mesh.GetBoneAxis(HeadBoneName, AXIS_Y).Z < 0.0)
		{
			NewRotation.Yaw += 32768;
			FeignRecoverAnim = 'Getup_B_V1';
		}
		else FeignRecoverAnim = 'Getup_F_V1';
		
		// Init wakeup anim.
		if (Mesh.AnimSets.Find(WakeUpAnimSet)==-1)
			Mesh.AnimSets.AddItem(WakeUpAnimSet);
		BodyStanceNodes[EAS_FullBody].bNoNotifies = true;
		BodyStanceNodes[EAS_FullBody].PlayCustomAnim(FeignRecoverAnim,0.025f,,,,true);
		
		SetRotation(NewRotation);
	}
}

final function vector PickNearestNode()
{
	local NavigationPoint N,Best;
	local float Dist,BestDist;
	
	foreach WorldInfo.AllNavigationPoints(class'NavigationPoint',N)
	{
		Dist = VSizeSq(N.Location-Location);
		if (Best==None || Dist<BestDist)
		{
			Best = N;
			BestDist = Dist;
		}
	}
	return (Best!=None ? Best.Location : Location);
}

simulated function bool SetFeignEndLocation(vector HitLocation, vector FeignLocation)
{
	local vector NewDest;

	if (SetLocation(HitLocation) && CheckValidLocation(FeignLocation))
	{
		return true;
	}

	// try crouching
	ForceCrouch();
	if (SetLocation(HitLocation) && CheckValidLocation(FeignLocation))
	{
		return true;
	}

	newdest = HitLocation + GetCollisionRadius() * vect(1,1,0);
	if (SetLocation(newdest) && CheckValidLocation(FeignLocation))
		return true;
	newdest = HitLocation + GetCollisionRadius() * vect(1,-1,0);
	if (SetLocation(newdest) && CheckValidLocation(FeignLocation))
		return true;
	newdest = HitLocation + GetCollisionRadius() * vect(-1,1,0);
	if (SetLocation(newdest) && CheckValidLocation(FeignLocation))
		return true;
	newdest = HitLocation + GetCollisionRadius() * vect(-1,-1,0);
	if (SetLocation(newdest) && CheckValidLocation(FeignLocation))
		return true;

	return false;
}

simulated function bool CheckValidLocation(vector FeignLocation)
{
	local vector HitLocation, HitNormal, DestFinalZ;

	// try trace down to dest
	if (Trace(HitLocation, HitNormal, Location, FeignLocation, false, vect(10,10,10),, TRACEFLAG_Bullet) == None)
	{
		return true;
	}

	// try trace straight up, then sideways to final location
	DestFinalZ = FeignLocation;
	FeignLocation.Z = Location.Z;
	if (Trace(HitLocation, HitNormal, DestFinalZ, FeignLocation, false, vect(10,10,10)) == None &&
		Trace(HitLocation, HitNormal, Location, DestFinalZ, false, vect(10,10,10),, TRACEFLAG_Bullet) == None)
	{
		return true;
	}
	return false;
}

simulated function SetPawnRBChannels(bool bRagdollMode)
{
	if (bRagdollMode)
	{
		Mesh.SetRBChannel(RBCC_DeadPawn);
		Mesh.SetRBCollidesWithChannel(RBCC_Default,TRUE);
		Mesh.SetRBCollidesWithChannel(RBCC_Pawn,FALSE);
		Mesh.SetRBCollidesWithChannel(RBCC_Vehicle,TRUE);
		Mesh.SetRBCollidesWithChannel(RBCC_Untitled3,FALSE);
		Mesh.SetRBCollidesWithChannel(RBCC_BlockingVolume,TRUE);
		Mesh.SetRBCollidesWithChannel(RBCC_DeadPawn, false);
	}
	else
	{
		Mesh.SetRBChannel(RBCC_Pawn);
		Mesh.SetRBCollidesWithChannel(RBCC_Default,FALSE);
		Mesh.SetRBCollidesWithChannel(RBCC_Pawn,FALSE);
		Mesh.SetRBCollidesWithChannel(RBCC_Vehicle,FALSE);
		Mesh.SetRBCollidesWithChannel(RBCC_Untitled3,TRUE);
		Mesh.SetRBCollidesWithChannel(RBCC_BlockingVolume,FALSE);
	}
}

simulated function PlayRagdollDeath(class<DamageType> DamageType, vector HitLoc)
{
	local TraceHitInfo HitInfo;
	local vector HitDirection;

	Mesh.SetHasPhysicsAssetInstance(false);
	Mesh.SetHasPhysicsAssetInstance(true);
	if (bFPLegsAttached)
	{
		bFPLegsAttached = false;
		DetachComponent(FPBodyMesh);
	}
	
	// Ensure we are always updating kinematic
	Mesh.MinDistFactorForKinematicUpdate = 0.0;

	PrepareRagdoll();

	if (InitRagdoll())
	{
		// Switch to a good RigidBody TickGroup to fix projectiles passing through the mesh
		// https://udn.unrealengine.com/questions/190581/projectile-touch-not-called.html
		//Mesh.SetTickGroup(TG_PostAsyncWork);
		SetTickGroup(TG_PostAsyncWork);

		// Allow all ragdoll bodies to collide with all physics objects (ie allow collision with things marked RigidBodyIgnorePawns)
		SetPawnRBChannels(true);

		// Call CheckHitInfo to give us a valid BoneName
		HitDirection = Normal(TearOffMomentum);
		CheckHitInfo(HitInfo, Mesh, HitDirection, HitLoc);

		// Play ragdoll death animation (bSkipReplication=TRUE)
		if (CanDoSpecialMove(SM_DeathAnim) && ClassIsChildOf(DamageType, class'KFDamageType'))
		{
			DoSpecialMove(SM_DeathAnim, TRUE,,,TRUE);
			KFSM_DeathAnim(SpecialMoves[SM_DeathAnim]).PlayDeathAnimation(DamageType, HitDirection, HitInfo.BoneName);
		}
		else
		{
			StopAllAnimations(); // stops non-RBbones from animating (fingers)
		}
	}
}

simulated function StartFeignDeathRecoveryAnim()
{
	if (FPBodyMesh!=None && !bFPLegsAttached && bOnFirstPerson && Class'ExtPlayerController'.Default.bShowFPLegs)
	{
		bFPLegsAttached = true;
		AttachComponent(FPBodyMesh);
	}

	bPlayingFeignDeathRecovery = false;

	// we're done with the ragdoll, so get rid of it
	Mesh.PhysicsWeight = 0.f;
	Mesh.PhysicsAssetInstance.SetAllBodiesFixed(TRUE);
	Mesh.PhysicsAssetInstance.SetFullAnimWeightBonesFixed(FALSE, Mesh);
	SetPawnRBChannels(FALSE);
	Mesh.bUpdateKinematicBonesFromAnimation=TRUE;

	// Turn collision on for cylinder and off for skelmeshcomp
	CylinderComponent.SetActorCollision(true, true);

	BodyStanceNodes[EAS_FullBody].PlayCustomAnim(FeignRecoverAnim,1.2f,,,,true);
	SetTimer(1.7f,false,'OnWakeUpFinished');
}

function bool CanBeRedeemed()
{
	return true;
}

simulated function OnWakeUpFinished();

function AddDefaultInventory()
{
	local KFPerk MyPerk;

	MyPerk = GetPerk();
	if (MyPerk != none)
		MyPerk.AddDefaultInventory(self);

	Super(KFPawn).AddDefaultInventory();
}

simulated event FellOutOfWorld(class<DamageType> dmgType)
{
	if (Role==ROLE_Authority && NextRedeemTimer<WorldInfo.TimeSeconds) // Make sure to not to spam deathmessages while ghosting.
		Super.FellOutOfWorld(dmgType);
}

simulated event OutsideWorldBounds()
{
	if (Role==ROLE_Authority && NextRedeemTimer<WorldInfo.TimeSeconds)
		Super.OutsideWorldBounds();
}

simulated function KFCharacterInfoBase GetCharacterInfo()
{
	if (ExtPlayerReplicationInfo(PlayerReplicationInfo)!=None)
		return ExtPlayerReplicationInfo(PlayerReplicationInfo).GetSelectedArch();
	return Super.GetCharacterInfo();
}

simulated function SetCharacterArch(KFCharacterInfoBase Info, optional bool bForce)
{
	local KFPlayerReplicationInfo KFPRI;

	KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo);
	if (Info != CharacterArch || bForce)
	{
		// Set Family Info
		CharacterArch = Info;
		CharacterArch.SetCharacterFromArch(self, KFPRI);
		class'ExtCharacterInfo'.Static.SetCharacterMeshFromArch(KFCharacterInfo_Human(CharacterArch), self, KFPRI);
		class'ExtCharacterInfo'.Static.SetFirstPersonArmsFromArch(KFCharacterInfo_Human(CharacterArch), self, KFPRI);

		SetCharacterAnimationInfo();

		// Sounds
		SoundGroupArch = Info.SoundGroupArch;

		if (WorldInfo.NetMode != NM_DedicatedServer)
		{
			// refresh weapon attachment (attachment bone may have changed)
			if (WeaponAttachmentTemplate != None)
			{
				WeaponAttachmentChanged(true);
			}
		}
		if (WorldInfo.NetMode != NM_DedicatedServer)
		{
			// Attach/Reattach flashlight components when mesh is set
			if (Flashlight == None && FlashLightTemplate != None)
			{
				Flashlight = new(self) Class'KFFlashlightAttachment' (FlashLightTemplate);
			}
			if (FlashLight != None)
			{
				Flashlight.AttachFlashlight(Mesh);
			}
		}
		if (CharacterArch != none)
		{
			if (CharacterArch.VoiceGroupArchName != "")
				VoiceGroupArch = class<KFPawnVoiceGroup>(class'ExtCharacterInfo'.Static.SafeLoadObject(CharacterArch.VoiceGroupArchName, class'Class'));
		}
	}
}

simulated state FeigningDeath
{
ignores FaceRotation, SetMovementPhysics;

	function SetSprinting(bool bNewSprintStatus)
	{
		bIsSprinting = false;
	}
	simulated event RigidBodyCollision(PrimitiveComponent HitComponent, PrimitiveComponent OtherComponent, const out CollisionImpactData RigidCollisionData, int ContactIndex)
	{
		// only check fall damage for Z axis collisions
		if (Abs(RigidCollisionData.ContactInfos[0].ContactNormal.Z) > 0.5)
		{
			Velocity = Mesh.GetRootBodyInstance().PreviousVelocity;
			TakeFallingDamage();
			// zero out the z velocity on the body now so that we don't get stacked collisions
			Velocity.Z = 0.0;
			Mesh.SetRBLinearVelocity(Velocity, false);
			Mesh.GetRootBodyInstance().PreviousVelocity = Velocity;
			Mesh.GetRootBodyInstance().Velocity = Velocity;
		}
	}
	simulated event bool CanDoSpecialMove(ESpecialMove AMove, optional bool bForceCheck)
	{
		return (bForceCheck ? Global.CanDoSpecialMove(AMove,bForceCheck) : false);
	}
	function bool CanBeGrabbed(KFPawn GrabbingPawn, optional bool bIgnoreFalling, optional bool bAllowSameTeamGrab)
	{
		return false;
	}
	simulated function OnWakeUpFinished()
	{
		if (Physics == PHYS_RigidBody)
			setPhysics(PHYS_Falling);
		Mesh.MinDistFactorForKinematicUpdate = default.Mesh.MinDistFactorForKinematicUpdate;
		GotoState('Auto');
	}

	event bool EncroachingOn(Actor Other)
	{
		// don't abort moves in ragdoll
		return false;
	}

	simulated function bool CanThrowWeapon()
	{
		return false;
	}

	simulated function Tick(float DeltaTime)
	{
		local rotator NewRotation;

		if (bPlayingFeignDeathRecovery)
		{
			if (PlayerController(Controller) != None)
			{
				// interpolate Controller yaw to our yaw so that we don't get our rotation snapped around when we get out of feign death
				NewRotation = Controller.Rotation;
				NewRotation.Yaw = RInterpTo(NewRotation, Rotation, DeltaTime, 2.0).Yaw;
				Controller.SetRotation(NewRotation);
			}
			Mesh.PhysicsWeight = FMax(Mesh.PhysicsWeight-(DeltaTime*2.f),0.f);
			if (Mesh.PhysicsWeight<=0)
				StartFeignDeathRecoveryAnim();
		}
	}

	simulated event BeginState(name PreviousStateName)
	{
		local KFWeapon UTWeap;

		// Abort current special move
		if (IsDoingSpecialMove())
			SpecialMoveHandler.EndSpecialMove();

		bCanPickupInventory = false;
		StopFiring();
		bNoWeaponFiring = true;

		UTWeap = KFWeapon(Weapon);
		if (UTWeap != None)
		{
			UTWeap.SetIronSights(false);
			UTWeap.PlayWeaponPutDown(0.5f);
		}
		if (WorldInfo.NetMode!=NM_Client)
		{
			if (ExtPlayerController(Controller)!=None)
				ExtPlayerController(Controller).EnterRagdollMode(true);
			else if (Controller!=None)
				Controller.ReplicatedEvent('RagdollMove');
		}
	}
	simulated function WeaponAttachmentChanged(optional bool bForceReattach)
	{
		// Keep weapon hidden!
		if (WeaponAttachment != None)
		{
			WeaponAttachment.DetachFrom(self);
			WeaponAttachment.Destroy();
			WeaponAttachment = None;
		}
	}
	function bool CanBeRedeemed()
	{
		if (bFeigningDeath)
			PlayFeignDeath(false,true);
		NextRedeemTimer = WorldInfo.TimeSeconds+0.25;
		return false;
	}
	simulated function EndState(name NextStateName)
	{
		local KFWeapon UTWeap;

		Mesh.AnimSets.RemoveItem(WakeUpAnimSet);
		BodyStanceNodes[EAS_FullBody].bNoNotifies = false;
		if (NextStateName != 'Dying')
		{
			bNoWeaponFiring = default.bNoWeaponFiring;
			bCanPickupInventory = default.bCanPickupInventory;
			
			UTWeap = KFWeapon(Weapon);
			if (UTWeap != None)
			{
				WeaponAttachmentTemplate = UTWeap.AttachmentArchetype;
				UTWeap.PlayWeaponEquip(0.5f);
			}

			Global.SetMovementPhysics();
			bPlayingFeignDeathRecovery = false;
			if (WorldInfo.NetMode!=NM_Client)
			{
				if (ExtPlayerController(Controller)!=None)
					ExtPlayerController(Controller).EnterRagdollMode(false);
				else if (Controller!=None)
					Controller.ReplicatedEvent('EndRagdollMove');
			}
			
			Global.WeaponAttachmentChanged();
		}
	}
}

// VS mode.
state TransformZed extends FeigningDeath
{
Ignores FaceRotation, SetMovementPhysics, UnsetFeignDeath, Tick, TakeDamage, Died;

	simulated event BeginState(name PreviousStateName)
	{
		bCanPickupInventory = false;
		bNoWeaponFiring = true;
		if (ExtPlayerController(Controller)!=None)
			ExtPlayerController(Controller).EnterRagdollMode(true);
		else if (Controller!=None)
			Controller.ReplicatedEvent('RagdollMove');

		SetTimer(2,false,'TransformToZed');
	}
	simulated function EndState(name NextStateName)
	{
	}
	function bool CanBeRedeemed()
	{
		return false;
	}
	function TransformToZed()
	{
		local VS_ZedRecentZed Z;

		if (Controller==None)
		{
			Destroy();
			return;
		}
		PlayFeignDeath(false);
		SetCollision(false,false);
		Z = Spawn(class'VS_ZedRecentZed',,,Location,Rotation,,true);
		if (Z==None)
		{
			Super.Died(None,Class'DamageType',Location);
			return;
		}
		else
		{
			Z.SetPhysics(PHYS_Falling);
			Z.LastStartTime = WorldInfo.TimeSeconds;
			Controller.Pawn = None;
			Controller.Possess(Z,false);
			WorldInfo.Game.ChangeTeam(Controller,255,true);
			WorldInfo.Game.SetPlayerDefaults(Z);
			if (ExtPlayerController(Controller)!=None)
				Controller.GoToState('RagdollMove');
			else if (Controller!=None)
				Controller.ReplicatedEvent('RagdollMove');
			Z.WakeUp();
			if (ExtPlayerReplicationInfo(Controller.PlayerReplicationInfo)!=None)
			{
				ExtPlayerReplicationInfo(Controller.PlayerReplicationInfo).PlayerHealth = Min(Z.Health,255);
				ExtPlayerReplicationInfo(Controller.PlayerReplicationInfo).PlayerHealthPercent = FloatToByte(float(Z.Health) / float(Z.HealthMax));
			}
		}
		Controller = None;
		Destroy();
	}
}

simulated final function InitFPLegs()
{
	local int i;

	bFPLegsInit = true;
	
	FPBodyMesh.AnimSets = CharacterArch.AnimSets;
	FPBodyMesh.SetAnimTreeTemplate(CharacterArch.AnimTreeTemplate);
	FPBodyMesh.SetSkeletalMesh(Mesh.SkeletalMesh);
	
	FPBodyMesh.SetActorCollision(false, false);
	FPBodyMesh.SetNotifyRigidBodyCollision(false);
	FPBodyMesh.SetTraceBlocking(false, false);

	for (i=0; i<Mesh.Materials.length; i++)
		FPBodyMesh.SetMaterial(i, Mesh.Materials[i]);

	FPBodyMesh.HideBoneByName('neck', PBO_None);
	FPBodyMesh.HideBoneByName('Spine2', PBO_None);
	FPBodyMesh.HideBoneByName('RightShoulder', PBO_None);
	FPBodyMesh.HideBoneByName('LeftShoulder', PBO_None);
}

// ForrestMarkX's third person backpack weapon and first person legs:
simulated function SetMeshVisibility(bool bVisible)
{
	Super.SetMeshVisibility(bVisible);

	if (Health>0)
	{
		bOnFirstPerson = !bVisible;
		if (AttachedBackItem!=None)
			AttachedBackItem.SetHidden(bOnFirstPerson);
		UpdateFPLegs();
	}
}

simulated final function UpdateFPLegs()
{
	if (FPBodyMesh!=None)
	{
		if (!bFPLegsAttached && Physics!=PHYS_RigidBody && bOnFirstPerson && Class'ExtPlayerController'.Default.bShowFPLegs)
		{
			bFPLegsAttached = true;
			AttachComponent(FPBodyMesh);
			
			if (!bFPLegsInit && CharacterArch!=None)
				InitFPLegs();
		}
		FPBodyMesh.SetHidden(!bOnFirstPerson || !Class'ExtPlayerController'.Default.bShowFPLegs);
	}
}

simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
	if (SkelComp==Mesh) // Do not allow first person legs eat up animation slots.
		Super.PostInitAnimTree(SkelComp);
}

simulated final function SetBackpackWeapon(class<KFWeapon> WC)
{
	local KFCharacterInfo_Human MyCharacter;
	local Rotator MyRot;
	local Vector MyPos;
	local name WM,B;
	local int i;

	BackpackWeaponClass = WC;
	if (WorldInfo.NetMode==NM_DedicatedServer)
		return;

	if (WC!=None)
	{
		if (AttachedBackItem==None)
		{
			AttachedBackItem = new(Self) class'SkeletalMeshComponent';
			AttachedBackItem.SetHidden(false);
			AttachedBackItem.SetLightingChannels(PawnLightingChannel);
		}
		AttachedBackItem.SetSkeletalMesh(WC.Default.AttachmentArchetype.SkelMesh);
		for (i=0; i<WC.Default.AttachmentArchetype.SkelMesh.Materials.length; i++)
		{
			AttachedBackItem.SetMaterial(i, WC.Default.AttachmentArchetype.SkelMesh.Materials[i]);
		}
		
		Mesh.DetachComponent(AttachedBackItem);
		
		MyCharacter = KFPlayerReplicationInfo(PlayerReplicationInfo).CharacterArchetypes[KFPlayerReplicationInfo(PlayerReplicationInfo).RepCustomizationInfo.CharacterIndex];
		WM = WC.Default.AttachmentArchetype.SkelMesh.Name;
		
		if (ClassIsChildOf(WC, class'KFWeap_Edged_Knife'))
		{
			MyPos = vect(0,0,10);
			MyRot = rot(-16384,-8192,0);
			B = 'LeftUpLeg';
		}
		else if (class<KFWeap_Welder>(WC) != none || class<KFWeap_Healer_Syringe>(WC) != none || class<KFWeap_Pistol_Medic>(WC) != none || class<KFWeap_SMG_Medic>(WC) != none || ClassIsChildOf(WC, class'KFWeap_PistolBase') || ClassIsChildOf(WC, class'KFWeap_SMGBase') || ClassIsChildOf(WC, class'KFWeap_ThrownBase'))
		{
			MyPos = vect(0,0,10);
			MyRot = rot(0,0,16384);

			B = 'LeftUpLeg';
		}
		else if (ClassIsChildOf(WC, class'KFWeap_MeleeBase'))
		{
			MyPos = vect(-5,15,0);
			MyRot = rot(0,0,0);
			
			if (class<KFWeap_Edged_Katana>(WC) != none || class<KFWeap_Edged_Zweihander>(WC) != none)
				MyPos.Z = -20;
				
			B = 'Spine';
		}
		else
		{
			MyPos = vect(-18.5,16.5,-18);
			MyRot = rot(0,0,0);

			if (MyCharacter == KFCharacterInfo_Human'CHR_Playable_ARCH.chr_DJSkully_archetype')
				MyRot.Roll = 8192;
				
			switch (WM)
			{
			case 'Wep_3rdP_MB500_Rig':
				MyPos.X = -45;
				break;
			case 'Wep_3rdP_M4Shotgun_Rig':
				MyPos.X = -25;
				break;
			case 'Wep_3rdP_SawBlade_Rig':
				MyPos.X = -75;
				MyRot.Roll = 16384;
				break;
			case 'Wep_3rdP_RPG7_Rig':
				MyPos.X = 10;
				break;
			}
			
			B = 'Spine2';
		}

		AttachedBackItem.SetTranslation(MyPos);
		AttachedBackItem.SetRotation(MyRot);
		Mesh.AttachComponent(AttachedBackItem, B);
		AttachedBackItem.SetHidden(bOnFirstPerson);
	}
	else if (AttachedBackItem!=None)
		AttachedBackItem.SetHidden(true);
}

simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
	FPBodyMesh.SetHidden(true);
	if (AttachedBackItem!=None)
		AttachedBackItem.SetHidden(true);
	Super.PlayDying(DamageType,HitLoc);
}

simulated function SetCharacterAnimationInfo()
{
	Super.SetCharacterAnimationInfo();

	if (!bFPLegsInit && bFPLegsAttached)
		InitFPLegs();
}

simulated function SetMeshLightingChannels(LightingChannelContainer NewLightingChannels)
{
	Super.SetMeshLightingChannels(NewLightingChannels);

	if (AttachedBackItem != none)
		AttachedBackItem.SetLightingChannels(NewLightingChannels);
	FPBodyMesh.SetLightingChannels(NewLightingChannels);
}

simulated function PlayWeaponswitch (Weapon OldWeapon, Weapon NewWeapon)
{
	Super.PlayWeaponswitch (OldWeapon, NewWeapon);

	if (WorldInfo.NetMode!=NM_Client)
	{
		PlayerOldWeapon = KFWeapon(OldWeapon);
		SetBackpackWeapon(PlayerOldWeapon!=None ? PlayerOldWeapon.Class : None);
	}
}

simulated function UpdateHealingSpeedBoostMod(ExtPlayerController Healer)
{
	local Ext_PerkFieldMedic MedPerk;
	
	MedPerk = GetMedicPerk(Healer);
	if (MedPerk == None)
		return;
	
	HealingSpeedBoostMod = Min(HealingSpeedBoostMod + MedPerk.GetHealingSpeedBoost(), MedPerk.GetMaxHealingSpeedBoost());
	SetTimer(MedPerk.GetHealingSpeedBoostDuration(),, nameOf(ResetHealingSpeedBoost));
	
	UpdateGroundSpeed();
}

simulated function float GetHealingSpeedModifier()
{
	return 1 + (float(HealingSpeedBoostMod) / 100);
}

simulated function ResetHealingSpeedBoost()
{
	HealingSpeedBoostMod = 0;
	UpdateGroundSpeed();

	if (IsTimerActive(nameOf(ResetHealingSpeedBoost)))
		ClearTimer(nameOf(ResetHealingSpeedBoost));
}

simulated function UpdateHealingDamageBoostMod(ExtPlayerController Healer)
{
	local Ext_PerkFieldMedic MedPerk;
	
	MedPerk = GetMedicPerk(Healer);
	if (MedPerk == None)
		return;
		
	HealingDamageBoostMod = Min(HealingDamageBoostMod + MedPerk.GetHealingDamageBoost(), MedPerk.GetMaxHealingDamageBoost());
	SetTimer(MedPerk.GetHealingDamageBoostDuration(),, nameOf(ResetHealingDamageBoost));
}

simulated function float GetHealingDamageBoostModifier()
{
	return 1 + (float(HealingDamageBoostMod) / 100);
}

simulated function ResetHealingDamageBoost()
{
	HealingDamageBoostMod = 0;
	if (IsTimerActive(nameOf(ResetHealingDamageBoost)))
		ClearTimer(nameOf(ResetHealingDamageBoost));
}

simulated function UpdateHealingShieldMod(ExtPlayerController Healer)
{
	local Ext_PerkFieldMedic MedPerk;
	
	MedPerk = GetMedicPerk(Healer);
	if (MedPerk == None)
		return;
		
	HealingShieldMod = Min(HealingShieldMod + MedPerk.GetHealingShield(), MedPerk.GetMaxHealingShield());
	SetTimer(MedPerk.GetHealingShieldDuration(),, nameOf(ResetHealingShield));
}

simulated function float GetHealingShieldModifier()
{
	return 1 - (float(HealingShieldMod) / 100);
}

simulated function ResetHealingShield()
{
	HealingShieldMod = 0;
	if (IsTimerActive(nameOf(ResetHealingShield)))
		ClearTimer(nameOf(ResetHealingShield));
}

function SacrificeExplode()
{
	local Ext_PerkDemolition DemoPerk;
	
	Super.SacrificeExplode();
	
	DemoPerk = Ext_PerkDemolition(ExtPlayerController(Controller).ActivePerkManager.CurrentPerk);
	if (DemoPerk != none)
		DemoPerk.bUsedSacrifice = true;
}

simulated function Ext_PerkFieldMedic GetMedicPerk(ExtPlayerController Healer)
{
	local Ext_PerkFieldMedic MedPerk;
	
	MedPerk = Ext_PerkFieldMedic(ExtPlayerController(Controller).ActivePerkManager.CurrentPerk);
	if (MedPerk != None) 
		return MedPerk;
		
	return None;
}

defaultproperties
{
	KnockbackResist=1

	// Ragdoll mode:
	bReplicateRigidBodyLocation=true
	bCanBecomeRagdoll=true
	InventoryManagerClass=class'ExtInventoryManager'
	WakeUpAnimSet=AnimSet'ZED_Clot_Anim.Alpha_Clot_Master'
	
	Begin Object Name=SpecialMoveHandler_0
		SpecialMoveClasses(SM_Emote)=class'ServerExt.ExtSM_Player_Emote'
	End Object
	
	DefaultInventory.Empty()
	DefaultInventory.Add(class'ExtWeap_Pistol_9mm')
	// DefaultInventory.Add(class'KFWeap_Pistol_9mm')
	DefaultInventory.Add(class'KFWeap_Healer_Syringe')
	DefaultInventory.Add(class'KFWeap_Welder')
	DefaultInventory.Add(class'KFInventory_Money')

	Begin Object Class=SkeletalMeshComponent Name=FP_BodyComp
		MinDistFactorForKinematicUpdate=0.0
		bSkipAllUpdateWhenPhysicsAsleep=True
		bIgnoreControllersWhenNotRendered=True
		bHasPhysicsAssetInstance=False
		bUpdateKinematicBonesFromAnimation=False
		bPerBoneMotionBlur=True
		bOverrideAttachmentOwnerVisibility=True
		bChartDistanceFactor=True
		DepthPriorityGroup=SDPG_Foreground
		RBChannel=RBCC_Pawn
		RBDominanceGroup=20
		HiddenGame=True
		bOnlyOwnerSee=True
		bAcceptsDynamicDecals=True
		bUseOnePassLightingOnTranslucency=True
		Translation=(X=-65.876999,Y=0.900000,Z=-95.500000)
		Scale=1.210000
		ScriptRigidBodyCollisionThreshold=200.000000
		PerObjectShadowCullDistance=4000.000000
		bAllowPerObjectShadows=True
		bAllowPerObjectShadowBatching=True
	End Object
	FPBodyMesh=FP_BodyComp
}

simulated function bool CanBeHealed()
{
	return true;
}