using System; /// /// Used to control the Citizen animation state. You don't have to use this to animate your citizen avatar, but our /// aim is to put everything you need in this class, so you can easily see what variables are available. /// [Title( "[KPTL] Animation Helper" )] [Category( "KPTL" )] [Icon( "directions_run" )] public sealed class AnimationHelper : Component, Component.ExecuteInEditor { /// /// The skinned model renderer that we'll apply all parameters to. /// [Property] public SkinnedModelRenderer Target { get; set; } /// /// Where are the eyes of our character? /// [Property] public GameObject EyeSource { get; set; } /// /// How tall are we? /// [Property, Range( 0.5f, 1.5f ), Title( "Avatar Height Scale" )] public float? Height { get; set; } /// /// Are we looking at something? Useful for stuff like cutscenes, where you want an NPC to stare at you. /// [Property, ToggleGroup( "LookAtEnabled", Label = "Look At" )] public bool LookAtEnabled { get; set; } = false; /// /// Which GameObject should we be looking at? /// [Property, ToggleGroup( "LookAtEnabled" )] public GameObject LookAt { get; set; } [Property, ToggleGroup( "LookAtEnabled" ), Range( 0, 1 )] public float EyesWeight { get; set; } = 1.0f; [Property, ToggleGroup( "LookAtEnabled" ), Range( 0, 1 )] public float HeadWeight { get; set; } = 1.0f; [Property, ToggleGroup( "LookAtEnabled" ), Range( 0, 1 )] public float BodyWeight { get; set; } = 1.0f; /// /// IK will try to place the limb where this GameObject is in the world. /// [Property, Group( "Inverse kinematics" ), Title( "Left Hand" )] public GameObject IkLeftHand { get; set; } /// [Property, Group( "Inverse kinematics" ), Title( "Right Hand" )] public GameObject IkRightHand { get; set; } /// [Property, Group( "Inverse kinematics" ), Title( "Left Foot" )] public GameObject IkLeftFoot { get; set; } /// [Property, Group( "Inverse kinematics" ), Title( "Right Foot" )] public GameObject IkRightFoot { get; set; } [Sync] private Vector3 LookDir { get; set; } protected override void OnUpdate() { if ( !Target.IsValid() ) return; if ( LookAtEnabled ) { if ( Network.IsOwner ) { LookDir = Scene.Camera.GameObject.WorldRotation.Forward; } WithLook( LookDir, EyesWeight, HeadWeight, BodyWeight ); } if ( Height.HasValue ) { Target.Set( "scale_height", Height.Value ); } if ( IkLeftHand.IsValid() && IkLeftHand.Active ) Target.SetIk( "hand_left", IkLeftHand.Transform.World ); else Target.ClearIk( "hand_left" ); if ( IkRightHand.IsValid() && IkRightHand.Active ) Target.SetIk( "hand_right", IkRightHand.Transform.World ); else Target.ClearIk( "hand_right" ); if ( IkLeftFoot.IsValid() && IkLeftFoot.Active ) Target.SetIk( "foot_left", IkLeftFoot.Transform.World ); else Target.ClearIk( "foot_left" ); if ( IkRightFoot.IsValid() && IkRightFoot.Active ) Target.SetIk( "foot_right", IkRightFoot.Transform.World ); else Target.ClearIk( "foot_right" ); } public void ProceduralHitReaction( DamageInfo info, float damageScale = 1.0f, Vector3 force = default ) { var boneId = info.Hitbox?.Bone?.Index ?? 0; var bone = Target.GetBoneObject( boneId ); var localToBone = bone.LocalPosition; if ( localToBone == Vector3.Zero ) localToBone = Vector3.One; Target.Set( "hit", true ); Target.Set( "hit_bone", boneId ); Target.Set( "hit_offset", localToBone ); Target.Set( "hit_direction", force.Normal ); Target.Set( "hit_strength", (force.Length / 1000.0f) * damageScale ); } /// /// The transform of the eyes, in world space. This is worked out from EyeSource is it's set. /// public Transform EyeWorldTransform { get { if ( EyeSource.IsValid() ) return EyeSource.Transform.World; return Transform.World; } } /// /// Have the player look at this point in the world /// public void WithLook( Vector3 lookDirection, float eyesWeight = 1.0f, float headWeight = 1.0f, float bodyWeight = 1.0f ) { Target.SetLookDirection( "aim_eyes", lookDirection, eyesWeight ); Target.SetLookDirection( "aim_head", lookDirection, headWeight ); Target.SetLookDirection( "aim_body", lookDirection, bodyWeight ); } /// /// Have the player animate moving with a set velocity (this doesn't move them! Your character controller is responsible for that) /// /// public void WithVelocity( Vector3 Velocity ) { var dir = Velocity; var forward = Target.WorldRotation.Forward.Dot( dir ); var sideward = Target.WorldRotation.Right.Dot( dir ); var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees(); Target.Set( "move_direction", angle ); Target.Set( "move_speed", Velocity.Length ); Target.Set( "move_groundspeed", Velocity.WithZ( 0 ).Length ); Target.Set( "move_y", sideward ); Target.Set( "move_x", forward ); Target.Set( "move_z", Velocity.z ); } /// /// Animates the wish for the character to move in a certain direction. For example, when in the air, your character will swing their arms in that direction. /// /// public void WithWishVelocity( Vector3 Velocity ) { var dir = Velocity; var forward = Target.WorldRotation.Forward.Dot( dir ); var sideward = Target.WorldRotation.Right.Dot( dir ); var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees(); Target.Set( "wish_direction", angle ); Target.Set( "wish_speed", Velocity.Length ); Target.Set( "wish_groundspeed", Velocity.WithZ( 0 ).Length ); Target.Set( "wish_y", sideward ); Target.Set( "wish_x", forward ); Target.Set( "wish_z", Velocity.z ); } /// /// Where are we aiming? /// public Rotation AimAngle { set { value = Target.WorldRotation.Inverse * value; var ang = value.Angles(); Target.Set( "aim_body_pitch", ang.pitch ); Target.Set( "aim_body_yaw", ang.yaw ); } } /// /// The weight of the aim angle, but specifically for the Citizen's eyes. /// public float AimEyesWeight { get => Target.GetFloat( "aim_eyes_weight" ); set => Target.Set( "aim_eyes_weight", value ); } /// /// The weight of the aim angle, but specifically for the Citizen's head. /// public float AimHeadWeight { get => Target.GetFloat( "aim_head_weight" ); set => Target.Set( "aim_head_weight", value ); } /// /// The weight of the aim angle, but specifically for the Citizen's body. /// public float AimBodyWeight { get => Target.GetFloat( "aim_body_weight" ); set => Target.Set( "aim_body_weight", value ); } /// /// How much the character is rotating in degrees per second, this controls feet shuffling. /// If rotating clockwise this should be positive, if rotating counter-clockwise this should be negative. /// public float MoveRotationSpeed { get => Target.GetFloat( "move_rotationspeed" ); set => Target.Set( "move_rotationspeed", value ); } [Obsolete( "Use MoveRotationSpeed" )] public float FootShuffle { get => Target.GetFloat( "move_shuffle" ); set => Target.Set( "move_shuffle", value ); } /// /// The scale of being ducked (crouched) (0 - 1) /// [Sync] public float DuckLevel { get => Target.GetFloat( "duck" ); set => Target.Set( "duck", value ); } /// /// How loud are we talking? /// public float VoiceLevel { get => Target.GetFloat( "voice" ); set => Target.Set( "voice", value ); } /// /// Are we sitting down? /// public bool IsSitting { get => Target.GetBool( "b_sit" ); set => Target.Set( "b_sit", value ); } /// /// Are we on the ground? /// public bool IsGrounded { get => Target.GetBool( "b_grounded" ); set => Target.Set( "b_grounded", value ); } /// /// Are we swimming? /// public bool IsSwimming { get => Target.GetBool( "b_swim" ); set => Target.Set( "b_swim", value ); } /// /// Are we climbing? /// public bool IsClimbing { get => Target.GetBool( "b_climbing" ); set => Target.Set( "b_climbing", value ); } /// /// Are we noclipping? /// public bool IsNoclipping { get => Target.GetBool( "b_noclip" ); set => Target.Set( "b_noclip", value ); } /// /// Is the weapon lowered? By default, this'll happen when the character hasn't been shooting for a while. /// public bool IsWeaponLowered { get => Target.GetBool( "b_weapon_lower" ); set => Target.Set( "b_weapon_lower", value ); } public enum HoldTypes { None, Pistol, Rifle, Shotgun, HoldItem, Punch, Swing, RPG } /// /// What kind of weapon are we holding? /// public HoldTypes HoldType { get => (HoldTypes)Target.GetInt( "holdtype" ); set => Target.Set( "holdtype", (int)value ); } public enum Hand { Both, Right, Left } /// /// What's the handedness of our weapon? Left handed, right handed, or both hands? This is only supported by some holdtypes, like Pistol, HoldItem. /// public Hand Handedness { get => (Hand)Target.GetInt( "holdtype_handedness" ); set => Target.Set( "holdtype_handedness", (int)value ); } /// /// Triggers a jump animation /// [Broadcast] public void TriggerJump() { Target.Set( "b_jump", true); } /// /// Triggers a weapon deploy animation /// public void TriggerDeploy() { Target.Set( "b_deploy", true ); } public enum MoveStyles { Auto, Walk, Run } /// /// We can force the model to walk or run, or let it decide based on the speed. /// public MoveStyles MoveStyle { get => (MoveStyles)Target.GetInt( "move_style" ); set => Target.Set( "move_style", (int)value ); } public enum SpecialMoveStyle { None, LedgeGrab, Roll, Slide } /// /// We can force the model to have a specific movement state, instead of just running around. /// is good for shimmying across a ledge. /// is good for a platformer game where the character is rolling around continuously. /// is good for a shooter game or a platformer where the character is sliding. /// public SpecialMoveStyle SpecialMove { get => (SpecialMoveStyle)Target.GetInt( "special_movement_states" ); set => Target.Set( "special_movement_states", (int)value ); } //Sitting public enum SittingStyle { None, Chair, Floor } /// /// How are we sitting down? /// public SittingStyle Sitting { get => (SittingStyle)Target.GetInt( "sit" ); set => Target.Set( "sit", (int)value ); } /// /// How far up are we sitting down from the floor? /// public float SittingOffsetHeight { get => Target.GetFloat( "sit_offset_height" ); set => Target.Set( "sit_offset_height", value ); } /// /// From 0-1, how much are we actually sitting down. /// public float SittingPose { get => Target.GetFloat( "sit_pose" ); set => Target.Set( "sit_pose", value ); } }