//============================================================================= // KFPawn_Customization //============================================================================= // Customization pawn, allows previewing characters before play //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC //============================================================================= class KFPawn_Customization extends KFPawn_Human native(Pawn); struct native sReplicatedMovementData { var vector NewLocation; var rotator NewRotation; }; var bool bPlayingEmote; /** Post-spawn replicated location (we skip actor property replication, this allows us to update after spawn) */ var repnotify sReplicatedMovementData ReplicatedMovementData; /** Post-spawn replicated hidden status, controlled by the server */ var repnotify bool bServerHidden; /** Local hidden variable, OR'd with bServerHidden */ var bool bLocalHidden; var AnimSet MaleCustomizationAnimSet; var AnimSet FemaleCustomizationAnimSet; var bool bUsingCustomizationPoint; // This is true if we have been placed on a KFCustomizationPoint cpptext { virtual UBOOL IsAliveAndWell() const; virtual void PostNetReceiveLocation(); UBOOL SetDesiredRotation( FRotator TargetDesiredRotation,UBOOL InLockDesiredRotation=FALSE,UBOOL InUnlockWhenReached=FALSE,FLOAT InterpolationTime=-1.000000,UBOOL bResetRotationRate=TRUE ); } replication { if( bNetInitial || bNetDirty ) ReplicatedMovementData, bServerHidden; } /** Updates visiblity of surrounding customization pawns so that none overlap */ native function UpdateCustomizationPawnVisibility(); /** Updates location and rotation on client */ simulated event ReplicatedEvent( name VarName ) { if( VarName == nameOf(ReplicatedMovementData) ) { OnMovementDataUpdated(); } else if( VarName == nameOf(bServerHidden) ) { SetHidden( bServerHidden || bLocalHidden ); UpdateCustomizationPawnVisibility(); } else { super.ReplicatedEvent( VarName ); } } /** Sets our post-spawn location and rotation */ function SetUpdatedMovementData( vector NewLoc, rotator NewRot ) { ReplicatedMovementData.NewLocation = NewLoc; ReplicatedMovementData.NewRotation = NewRot; // Set directly on listen server OnMovementDataUpdated(); // Update on network immediately bForceNetUpdate = true; } /** Sets our updated movement data on client/listen server */ simulated function OnMovementDataUpdated() { SetLocation( ReplicatedMovementData.NewLocation ); SetRotation( ReplicatedMovementData.NewRotation ); // Update visibility on listen server/clients if( WorldInfo.NetMode != NM_DedicatedServer ) { UpdateCustomizationPawnVisibility(); } } /** Sets the server-controlled hidden status (we only care about bServerHidden=true) */ function SetServerHidden( bool bNewHidden ) { bServerHidden = bNewHidden; // Set directly on listen server if( WorldInfo.NetMode != NM_DedicatedServer ) { // Only hide if the server tells us to SetHidden( bServerHidden || bLocalHidden ); UpdateCustomizationPawnVisibility(); } // Update on network immediately bForceNetUpdate = true; } /** Overridden, we don't want anything to stomp on SetUpdatedMovementData() */ function ClientSetRotation( rotator NewRotation ) {} simulated function FaceRotation(rotator NewRotation, float DeltaTime) {} /********************************************************************************************* Initialization ********************************************************************************************* */ /** Disable foot IK */ simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp) { Super.PostInitAnimTree(SkelComp); // Disable foot IK so we have have straight legs for this character. When // bAllowStretching is set the knees must be bent a little. if ( IKFootLeft != None ) { IKFootLeft.SetSkelControlActive(false); } if ( IKFootRight != None ) { IKFootRight.SetSkelControlActive(false); } SetHeadScale(IntendedHeadScale, CurrentHeadScale); } /** Always use the customization anim set */ simulated function SetCharacterAnimationInfo() { local KFCharacterInfo_Human CharInfoHuman; super.SetCharacterAnimationInfo(); // Character Animation CharInfoHuman = KFCharacterInfo_Human( GetCharacterInfo() ); if( CharInfoHuman != none && CharInfoHuman.bIsFemale ) { Mesh.AnimSets.AddItem(FemaleCustomizationAnimSet); } else { Mesh.AnimSets.AddItem(MaleCustomizationAnimSet); } Mesh.UpdateAnimations(); PlayRandomIdleAnimation(true); } function AttachWeaponByItemDefinition( int ItemDefinition ) { local class WeaponDef; local int ItemINdex; local KFWeaponAttachment WeaponPreview; //find weapon def ItemIndex = class'KFWeaponSkinList'.default.Skins.Find('Id', ItemDefinition); if(ItemIndex == INDEX_NONE) { `log("Could not find item" @ItemDefinition); return; } WeaponDef = class'KFWeaponSkinList'.default.Skins[ItemIndex].WeaponDef; if(WeaponDef == none) { `log("Weapon def NONE for : " @ItemDefinition); return; } //load in and add object . WeaponPreview = KFWeaponAttachment ( DynamicLoadObject( WeaponDef.default.AttachmentArchtypePath, class'KFWeaponAttachment' ) ); //attatch it to player WeaponAttachmentTemplate = WeaponPreview; WeaponAttachmentChanged(); //setweapon skin WeaponAttachment.SetWeaponSkin(ItemDefinition); } simulated function PlayRandomIdleAnimation(optional bool bNewCharacter) { local byte AnimIndex; local name AnimName; local AnimSet AnimSet; local float BlendInTime; AnimSet = Mesh.AnimSets[Mesh.AnimSets.Length - 1]; AnimIndex = Rand(AnimSet.Sequences.Length); AnimName = AnimSet.Sequences[AnimIndex].SequenceName; BlendInTime = (bNewCharacter) ? 0.f : 0.4; // Briefly turn off notify so that PlayCustomAnim won't call OnAnimEnd (e.g. character swap) BodyStanceNodes[EAS_FullBody].SetActorAnimEndNotification( FALSE ); BodyStanceNodes[EAS_FullBody].PlayCustomAnim(AnimName, 1.f, BlendInTime, 0.4, false, true); BodyStanceNodes[EAS_FullBody].SetActorAnimEndNotification( TRUE ); // Since the emote is being cancelled clear this flag too; lets the player queue up another emote again if they want bPlayingEmote = false; } simulated function PlayEmoteAnimation(optional bool bNewCharacter) { local name AnimName; local float BlendInTime; if (bPlayingEmote) { return; } bPlayingEmote = true; AnimName = class'KFEmoteList'.static.GetUnlockedEmote( class'KFEmoteList'.static.GetEquippedEmoteId() ); BlendInTime = (bNewCharacter) ? 0.f : 0.4; // Briefly turn off notify so that PlayCustomAnim won't call OnAnimEnd (e.g. character swap) BodyStanceNodes[EAS_FullBody].SetActorAnimEndNotification( FALSE ); BodyStanceNodes[EAS_FullBody].PlayCustomAnim(AnimName, 1.f, BlendInTime, 0.4, false, true); BodyStanceNodes[EAS_FullBody].SetActorAnimEndNotification( TRUE ); } /** Called from SkeletalMeshComponent::PlayParticleEffect() */ simulated function OnAnimNotifyParticleSystemSpawned( const AnimNotify_PlayParticleEffect AnimNotifyData, ParticleSystemComponent PSC ) { if (bPlayingEmote) { PSC.bUseAsOccluder = true; } } simulated event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime) { bPlayingEmote = false; PlayRandomIdleAnimation(); } simulated function NotifyTeamChanged() { // Applies Character Info for < ROLE_Authority if( PlayerReplicationInfo != none && CharacterArch == none ) { SetCharacterArch(GetCharacterInfo()); } } /** If the customization point is created after the pawn, relocate our customization pawn to a customization point */ function bool MoveToCustomizationPoint() { local NavigationPoint BestStartSpot; local KFGameInfo KFGI; local PlayerController PC; KFGI = KFGameInfo( WorldInfo.Game ); PC = PlayerController( Controller ); if( PC != none ) { BestStartSpot = KFGI.FindCustomizationStart( PC ); // If a customization point wasn't found or if SetLocation fails, exit. if( BestStartSpot == none ) { return false; } // Update our location/rotation data SetUpdatedMovementData( BestStartSpot.Location, BestStartSpot.Rotation ); // initialize and start it up KFPlayerCamera(PC.PlayerCamera).CustomizationCam.bInitialize = false; bUsingCustomizationPoint = true; return true; } return false; } /** Customization Pawns cannot take damage */ event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { return; } /********************************************************************************************* Customizing ********************************************************************************************* */ /** Set basic variables for the newly created Customization Pawn, derived from RestartPlayer */ function InitializeCustomizationPawn( PlayerController NewController, NavigationPoint BestStartSpot ) { if (Controller != None) { Controller.UnPossess(); } PossessedBy( NewController, false ); NewController.SetViewTarget( self ); NewController.SetCameraMode( 'Customization' ); // initialize and start it up bUsingCustomizationPoint = ( KFCustomizationPoint( BestStartSpot ) != none ); // Need to update our client-side movement data SetUpdatedMovementData( BestStartSpot.Location, BestStartSpot.Rotation ); } function Reset(); defaultproperties { MaleCustomizationAnimSet=AnimSet'CHR_BaseMale_ANIM.CS_Male' FemaleCustomizationAnimSet=AnimSet'CHR_BaseFemale_ANIM.CS_Female' bDisableTurnInPlace=true bEnableAimOffset=false bSkipActorPropertyReplication=true bReplicateMovement=false bDisableMeshRotationSmoothing=true // Default the customization pawn to walking so that IK is enabled Physics=PHYS_Walking // artificial nudge -3uu to align to floor w/o IK Begin Object Name=KFPawnSkeletalMeshComponent Translation=(Z=-89) End Object }