using FarseerPhysics; using FarseerPhysics.Dynamics.Joints; using Microsoft.Xna.Framework; using System; using System.Xml.Linq; namespace Barotrauma { class FishAnimController : AnimController { //amplitude and wave length of the "sine wave" swimming animation //if amplitude = 0, sine wave animation isn't used private float waveAmplitude; private float waveLength; private float steerTorque; private bool rotateTowardsMovement; private bool mirror, flip; private float flipTimer; private float? footRotation; private float deathAnimTimer, deathAnimDuration = 5.0f; public FishAnimController(Character character, XElement element) : base(character, element) { waveAmplitude = ConvertUnits.ToSimUnits(element.GetAttributeFloat("waveamplitude", 0.0f)); waveLength = ConvertUnits.ToSimUnits(element.GetAttributeFloat("wavelength", 0.0f)); steerTorque = element.GetAttributeFloat("steertorque", 25.0f); flip = element.GetAttributeBool("flip", true); mirror = element.GetAttributeBool("mirror", false); float footRot = element.GetAttributeFloat("footrotation", float.NaN); if (float.IsNaN(footRot)) { footRotation = null; } else { footRotation = MathHelper.ToRadians(footRot); } rotateTowardsMovement = element.GetAttributeBool("rotatetowardsmovement", true); } public override void UpdateAnim(float deltaTime) { if (Frozen) return; if (character.IsDead || character.IsUnconscious || character.Stun > 0.0f) { Collider.FarseerBody.FixedRotation = false; if (character.IsRemotePlayer) { MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition; MainLimb.pullJoint.Enabled = true; } else { Collider.LinearVelocity = (MainLimb.SimPosition - Collider.SimPosition) * 60.0f; Collider.SmoothRotate(MainLimb.Rotation); } if (character.IsDead && deathAnimTimer < deathAnimDuration) { deathAnimTimer += deltaTime; UpdateDying(deltaTime); } return; } //re-enable collider if (!Collider.Enabled) { var lowestLimb = FindLowestLimb(); Collider.SetTransform(new Vector2( Collider.SimPosition.X, Math.Max(lowestLimb.SimPosition.Y + (Collider.radius + Collider.height / 2), Collider.SimPosition.Y)), 0.0f); Collider.Enabled = true; } ResetPullJoints(); if (strongestImpact > 0.0f) { character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f); strongestImpact = 0.0f; } if (inWater) { Collider.FarseerBody.FixedRotation = false; UpdateSineAnim(deltaTime); } else if (currentHull != null && CanEnterSubmarine) { if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, 0.0f)) > 0.001f) { //rotate collider back upright Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f) * 60.0f; Collider.FarseerBody.FixedRotation = false; } else { Collider.FarseerBody.FixedRotation = true; } UpdateWalkAnim(deltaTime); } if (!character.IsRemotePlayer) { if (mirror || !inWater) { if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.5f) { TargetDir = Direction.Right; } else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.5f) { TargetDir = Direction.Left; } } else { Limb head = GetLimb(LimbType.Head); if (head == null) head = GetLimb(LimbType.Torso); float rotation = MathUtils.WrapAngleTwoPi(head.Rotation); rotation = MathHelper.ToDegrees(rotation); if (rotation < 0.0f) rotation += 360; if (rotation > 20 && rotation < 160) { TargetDir = Direction.Left; } else if (rotation > 200 && rotation < 340) { TargetDir = Direction.Right; } } } if (character.SelectedCharacter != null) DragCharacter(character.SelectedCharacter); if (!flip) return; flipTimer += deltaTime; if (TargetDir != Direction.None && TargetDir != dir) { if (flipTimer > 1.0f || character.IsRemotePlayer) { Flip(); if (mirror || !inWater) Mirror(); flipTimer = 0.0f; } } } private float eatTimer = 0.0f; public override void DragCharacter(Character target) { if (target == null) return; Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue); if (mouthLimb == null) mouthLimb = GetLimb(LimbType.Head); if (mouthLimb == null) { DebugConsole.ThrowError("Character \"" + character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)"); return; } Character targetCharacter = target; float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f; eatTimer += (float)Timing.Step * eatSpeed; Vector2 mouthPos = mouthLimb.SimPosition; if (mouthLimb.MouthPos.HasValue) { float cos = (float)Math.Cos(mouthLimb.Rotation); float sin = (float)Math.Sin(mouthLimb.Rotation); mouthPos += new Vector2( mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin, mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos); } Vector2 attackSimPosition = character.Submarine == null ? ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float limbDist = limbDiff.Length(); if (limbDist < 1.0f) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation); targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength); if (eatTimer % 1.0f < 0.5f && (eatTimer - (float)Timing.Step * eatSpeed) % 1.0f > 0.5f) { //apply damage to the target character to get some blood particles flying targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), 10.0f, false); //keep severing joints until there is only one limb left LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered && l.CanBeSevered); if (nonSeveredJoints.Length == 0) { //only one limb left, the character is now full eaten Entity.Spawner.AddToRemoveQueue(targetCharacter); character.SelectedCharacter = null; character.Health += 10.0f; } else //sever a random joint { targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]); } } } else { character.SelectedCharacter = null; } } void UpdateSineAnim(float deltaTime) { movement = TargetMovement*swimSpeed; MainLimb.pullJoint.Enabled = true; MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition; if (movement.LengthSquared() < 0.00001f) return; float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2; if (rotateTowardsMovement) { Collider.SmoothRotate(movementAngle, 25.0f); MainLimb.body.SmoothRotate(movementAngle, steerTorque); } else { Collider.SmoothRotate(HeadAngle * Dir, 25.0f); MainLimb.body.SmoothRotate(HeadAngle * Dir, steerTorque); } Limb tail = GetLimb(LimbType.Tail); if (tail != null && waveAmplitude > 0.0f) { walkPos -= movement.Length(); float waveRotation = (float)Math.Sin(walkPos / waveLength); tail.body.ApplyTorque(waveRotation * tail.Mass * 100.0f * waveAmplitude); } for (int i = 0; i < Limbs.Length; i++) { if (Limbs[i].SteerForce <= 0.0f) continue; Vector2 pullPos = Limbs[i].pullJoint == null ? Limbs[i].SimPosition : Limbs[i].pullJoint.WorldAnchorA; Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos); } Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, 0.5f); floorY = Limbs[0].SimPosition.Y; } void UpdateWalkAnim(float deltaTime) { movement = MathUtils.SmoothStep(movement, TargetMovement * walkSpeed, 0.2f); float mainLimbHeight, mainLimbAngle; if (MainLimb.type == LimbType.Torso) { mainLimbHeight = TorsoPosition; mainLimbAngle = torsoAngle; } else { mainLimbHeight = HeadPosition; mainLimbAngle = headAngle; } MainLimb.body.SmoothRotate(mainLimbAngle * Dir, 50.0f); Collider.LinearVelocity = new Vector2( movement.X, Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y); MainLimb.MoveToPos(GetColliderBottom() + Vector2.UnitY * mainLimbHeight, 10.0f); MainLimb.pullJoint.Enabled = true; MainLimb.pullJoint.WorldAnchorB = GetColliderBottom() + Vector2.UnitY * mainLimbHeight; walkPos -= MainLimb.LinearVelocity.X * 0.05f; Vector2 transformedStepSize = new Vector2( (float)Math.Cos(walkPos) * stepSize.X * 3.0f, (float)Math.Sin(walkPos) * stepSize.Y * 2.0f); foreach (Limb limb in Limbs) { switch (limb.type) { case LimbType.LeftFoot: case LimbType.RightFoot: Vector2 footPos = new Vector2(limb.SimPosition.X, MainLimb.SimPosition.Y - mainLimbHeight); if (limb.RefJointIndex>-1) { RevoluteJoint refJoint = LimbJoints[limb.RefJointIndex]; footPos.X = refJoint.WorldAnchorA.X; } footPos.X += limb.StepOffset.X * Dir; footPos.Y += limb.StepOffset.Y; if (limb.type == LimbType.LeftFoot) { limb.MoveToPos(footPos +new Vector2( transformedStepSize.X + movement.X * 0.1f, (transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f), 8.0f); } else if (limb.type == LimbType.RightFoot) { limb.MoveToPos(footPos + new Vector2( -transformedStepSize.X + movement.X * 0.1f, (-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f), 8.0f); } if (footRotation != null) limb.body.SmoothRotate((float)footRotation * Dir, 50.0f); break; case LimbType.LeftLeg: case LimbType.RightLeg: if (legTorque != 0.0f) limb.body.ApplyTorque(limb.Mass * legTorque * Dir); break; } } } void UpdateDying(float deltaTime) { Limb head = GetLimb(LimbType.Head); Limb tail = GetLimb(LimbType.Tail); if (head != null && !head.IsSevered) head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * Math.Sin(walkPos)) * 10.0f); if (tail != null && !tail.IsSevered) tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (float)Math.Sin(walkPos)) * 10.0f); walkPos += deltaTime * 5.0f; Vector2 centerOfMass = GetCenterOfMass(); foreach (Limb limb in Limbs) { if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered) continue; limb.body.ApplyForce((centerOfMass - limb.SimPosition) * (float)(Math.Sin(walkPos) * Math.Sqrt(limb.Mass)) * 10.0f); } } public override void Flip() { base.Flip(); foreach (Limb l in Limbs) { if (!l.DoesFlip) continue; l.body.SetTransform(l.SimPosition, -l.body.Rotation); } } private void Mirror() { Vector2 centerOfMass = GetCenterOfMass(); foreach (Limb l in Limbs) { TrySetLimbPosition(l, centerOfMass, new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y), true); } } } }