Пример #1
0
 public void DrawDamageModifiers(SpriteBatch spriteBatch, Camera cam, Vector2 startPos, bool isScreenSpace)
 {
     foreach (var modifier in damageModifiers)
     {
         float   rotation = -body.TransformedRotation + GetArmorSectorRotationOffset(modifier.ArmorSectorInRadians) * Dir;
         Vector2 forward  = VectorExtensions.Forward(rotation);
         float   size     = ConvertUnits.ToDisplayUnits(body.GetSize().Length() / 2);
         if (isScreenSpace)
         {
             size *= cam.Zoom;
         }
         Color color = modifier.DamageMultiplier > 1 ? Color.Red : Color.GreenYellow;
         int   width = 4;
         if (!isScreenSpace)
         {
             width = (int)Math.Round(width / cam.Zoom);
         }
         GUI.DrawLine(spriteBatch, startPos, startPos + Vector2.Normalize(forward) * size, color, width: width);
         int thickness = 2;
         if (!isScreenSpace)
         {
             thickness = (int)Math.Round(thickness / cam.Zoom);
         }
         ShapeExtensions.DrawSector(spriteBatch, startPos, size, GetArmorSectorSize(modifier.ArmorSectorInRadians) * Dir, 40, color, rotation + MathHelper.Pi, thickness);
     }
 }
Пример #2
0
        private void OperateRepairTool(float deltaTime)
        {
            character.CursorPosition = Item.WorldPosition;
            if (character.Submarine != null)
            {
                character.CursorPosition -= character.Submarine.Position;
            }
            if (repairTool.Item.RequireAimToUse)
            {
                character.SetInput(InputType.Aim, false, true);
            }
            Vector2 fromToolToTarget = Item.Position - repairTool.Item.Position;

            if (fromToolToTarget.LengthSquared() < MathUtils.Pow(repairTool.Range / 2, 2))
            {
                // Too close -> steer away
                character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - Item.SimPosition) / 2);
            }
            else
            {
                character.AIController.SteeringManager.Reset();
            }
            if (VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
            {
                repairTool.Use(deltaTime, character);
            }
        }
Пример #3
0
        public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
        {
            if (armorSector == Vector2.Zero)
            {
                return(false);
            }
            float rotation   = body.TransformedRotation;
            float offset     = (MathHelper.PiOver2 - GetArmorSectorRotationOffset(armorSector)) * Dir;
            float hitAngle   = VectorExtensions.Angle(VectorExtensions.Forward(rotation + offset), SimPosition - simPosition);
            float sectorSize = GetArmorSectorSize(armorSector);

            return(hitAngle < sectorSize / 2);
        }
 private void Attack(float deltaTime)
 {
     character.CursorPosition = Enemy.Position;
     if (Weapon.RequireAimToUse)
     {
         character.SetInput(InputType.Aim, false, true);
     }
     if (WeaponComponent is MeleeWeapon meleeWeapon)
     {
         if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
         {
             character.SetInput(InputType.Shoot, false, true);
             Weapon.Use(deltaTime, character);
         }
     }
     else
     {
         if (WeaponComponent is RepairTool repairTool)
         {
             if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range)
             {
                 return;
             }
         }
         if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
         {
             if (myBodies == null)
             {
                 myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
             }
             var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
             if (pickedBody != null)
             {
                 Character target = null;
                 if (pickedBody.UserData is Character c)
                 {
                     target = c;
                 }
                 else if (pickedBody.UserData is Limb limb)
                 {
                     target = limb.character;
                 }
                 if (target != null && target == Enemy)
                 {
                     character.SetInput(InputType.Shoot, false, true);
                     Weapon.Use(deltaTime, character);
                 }
             }
         }
     }
 }
Пример #5
0
        public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
        {
            if (armorSector == Vector2.Zero)
            {
                return(false);
            }
            //sector 360 degrees or more -> always hits
            if (Math.Abs(armorSector.Y - armorSector.X) >= MathHelper.TwoPi)
            {
                return(true);
            }
            float rotation   = body.TransformedRotation;
            float offset     = (MathHelper.PiOver2 - GetArmorSectorRotationOffset(armorSector)) * Dir;
            float hitAngle   = VectorExtensions.Angle(VectorExtensions.Forward(rotation + offset), SimPosition - simPosition);
            float sectorSize = GetArmorSectorSize(armorSector);

            return(hitAngle < sectorSize / 2);
        }
Пример #6
0
        private void Attack(float deltaTime)
        {
            character.CursorPosition = Enemy.Position;
            if (!character.CanSeeCharacter(Enemy))
            {
                return;
            }
            if (Weapon.RequireAimToUse)
            {
                bool isOperatingButtons = false;
                if (SteeringManager == PathSteering)
                {
                    var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
                    if (door != null && !door.IsOpen && !door.IsBroken)
                    {
                        isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents <Controller>(true).Any();
                    }
                }
                if (!isOperatingButtons)
                {
                    character.SetInput(InputType.Aim, false, true);
                }
            }
            bool isFacing = character.AnimController.Dir > 0 && Enemy.WorldPosition.X > character.WorldPosition.X || character.AnimController.Dir < 0 && Enemy.WorldPosition.X < character.WorldPosition.X;

            if (!isFacing)
            {
                aimTimer = Rand.Range(1f, 1.5f);
            }
            if (aimTimer > 0)
            {
                aimTimer -= deltaTime;
                return;
            }
            if (WeaponComponent is MeleeWeapon meleeWeapon)
            {
                if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
                {
                    character.SetInput(InputType.Shoot, false, true);
                    Weapon.Use(deltaTime, character);
                }
            }
            else
            {
                if (WeaponComponent is RepairTool repairTool)
                {
                    if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range)
                    {
                        return;
                    }
                }
                if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
                {
                    if (myBodies == null)
                    {
                        myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
                    }

                    var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
                    var pickedBody          = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
                    if (pickedBody != null)
                    {
                        Character target = null;
                        if (pickedBody.UserData is Character c)
                        {
                            target = c;
                        }
                        else if (pickedBody.UserData is Limb limb)
                        {
                            target = limb.character;
                        }
                        if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
                        {
                            character.SetInput(InputType.Shoot, false, true);
                            Weapon.Use(deltaTime, character);
                            float reloadTime = 0;
                            if (WeaponComponent is RangedWeapon rangedWeapon)
                            {
                                reloadTime = rangedWeapon.Reload;
                            }
                            if (WeaponComponent is MeleeWeapon mw)
                            {
                                reloadTime = mw.Reload;
                            }
                            aimTimer = reloadTime * Rand.Range(1f, 1.5f);
                        }
                    }
                }
            }
        }
Пример #7
0
        public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
        {
            Gap leak = objective.OperateTarget as Gap;

            if (leak == null)
            {
                return(true);
            }

            Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
            float   dist           = fromItemToLeak.Length();

            //too far away -> consider this done and hope the AI is smart enough to move closer
            if (dist > Range * 3.0f)
            {
                return(true);
            }

            // TODO: use the collider size?
            if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
                Math.Abs(fromItemToLeak.X) < 100.0f && fromItemToLeak.Y < 0.0f && fromItemToLeak.Y > -150.0f)
            {
                ((HumanoidAnimController)character.AnimController).Crouching = true;
            }

            //steer closer if almost in range
            if (dist > Range)
            {
                Vector2 standPos = new Vector2(Math.Sign(-fromItemToLeak.X), Math.Sign(-fromItemToLeak.Y)) / 2;
                if (!character.AnimController.InWater)
                {
                    if (leak.IsHorizontal)
                    {
                        standPos.X *= 2;
                        standPos.Y  = 0;
                    }
                    else
                    {
                        standPos.X = 0;
                    }
                }
                if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
                {
                    if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
                    {
                        Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
                        character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
                    }
                    else
                    {
                        character.AIController.SteeringManager.SteeringSeek(standPos);
                    }
                }
                else
                {
                    character.AIController.SteeringManager.SteeringSeek(standPos);
                }
            }
            else
            {
                if (dist < Range / 2)
                {
                    // Too close -> steer away
                    character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
                }
                else if (dist <= Range)
                {
                    // In range
                    character.AIController.SteeringManager.Reset();
                }
                else
                {
                    return(false);
                }
            }
            sinTime += deltaTime;
            character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
            if (item.RequireAimToUse)
            {
                bool isOperatingButtons = false;
                if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
                {
                    var door = indoorSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
                    if (door != null && !door.IsOpen)
                    {
                        isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents <Controller>(true).Any();
                    }
                }
                if (!isOperatingButtons)
                {
                    character.SetInput(InputType.Aim, false, true);
                }
            }
            // Press the trigger only when the tool is approximately facing the target.
            var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);

            if (angle < MathHelper.PiOver4)
            {
                character.SetInput(InputType.Shoot, false, true);
                Use(deltaTime, character);
            }

            bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
                             (leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);

            if (leakFixed && leak.FlowTargetHull != null)
            {
                sinTime = 0;
                if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
                {
                    character.Speak(TextManager.GetWithVariable("DialogLeaksFixed", "[roomname]", leak.FlowTargetHull.DisplayName, true), null, 0.0f, "leaksfixed", 10.0f);
                }
                else
                {
                    character.Speak(TextManager.GetWithVariable("DialogLeakFixed", "[roomname]", leak.FlowTargetHull.DisplayName, true), null, 0.0f, "leakfixed", 10.0f);
                }
            }

            return(leakFixed);
        }
Пример #8
0
        public void Draw(SpriteBatch spriteBatch, Camera cam, Color?overrideColor = null)
        {
            float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f;
            Color color      = new Color(brightness, brightness, brightness);

            color = overrideColor ?? color;

            if (isSevered)
            {
                if (severedFadeOutTimer > SeveredFadeOutTime)
                {
                    return;
                }
                else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f)
                {
                    color *= SeveredFadeOutTime - severedFadeOutTimer;
                }
            }

            body.Dir = Dir;

            bool hideLimb = wearingItems.Any(w => w != null && w.HideLimb);

            body.UpdateDrawPosition();

            if (!hideLimb)
            {
                var activeSprite = ActiveSprite;
                if (DeformSprite != null && activeSprite == DeformSprite.Sprite)
                {
                    if (Deformations != null && Deformations.Any())
                    {
                        var deformation = SpriteDeformation.GetDeformation(Deformations, DeformSprite.Size);
                        DeformSprite.Deform(deformation);
                    }
                    else
                    {
                        DeformSprite.Reset();
                    }
                    body.Draw(DeformSprite, cam, Vector2.One * Scale * TextureScale, color);
                }
                else
                {
                    body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale);
                }
            }

            if (LightSource != null)
            {
                LightSource.Position          = body.DrawPosition;
                LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
            }
            float          depthStep    = 0.000001f;
            WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables);
            SpriteEffects  spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;

            if (onlyDrawable == null)
            {
                if (HuskSprite != null && (character.SpeciesName == "Humanhusk" || (character.SpeciesName == "Human" &&
                                                                                    character.CharacterHealth.GetAffliction <AfflictionHusk>("huskinfection")?.State == AfflictionHusk.InfectionState.Active)))
                {
                    DrawWearable(HuskSprite, depthStep, spriteBatch, color, spriteEffect);
                }
                foreach (WearableSprite wearable in OtherWearables)
                {
                    DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect);
                    //if there are multiple sprites on this limb, make the successive ones be drawn in front
                    depthStep += 0.000001f;
                }
            }
            foreach (WearableSprite wearable in WearingItems)
            {
                if (onlyDrawable != null && onlyDrawable != wearable)
                {
                    continue;
                }
                DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect);
                //if there are multiple sprites on this limb, make the successive ones be drawn in front
                depthStep += 0.000001f;
            }

            if (damageOverlayStrength > 0.0f && DamagedSprite != null && !hideLimb)
            {
                float depth = ActiveSprite.Depth - 0.0000015f;

                // TODO: enable when the damage overlay textures have been remade.
                //DamagedSprite.Draw(spriteBatch,
                //    new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
                //    color * Math.Min(damageOverlayStrength, 1.0f), ActiveSprite.Origin,
                //    -body.DrawRotation,
                //    1.0f, spriteEffect, depth);
            }

            if (GameMain.DebugDraw)
            {
                if (pullJoint != null)
                {
                    Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB);
                    GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.Red, true);
                }
                var bodyDrawPos = body.DrawPosition;
                bodyDrawPos.Y = -bodyDrawPos.Y;
                if (IsStuck)
                {
                    Vector2 from = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorA);
                    from.Y = -from.Y;
                    Vector2 to = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorB);
                    to.Y = -to.Y;
                    var localFront = body.GetLocalFront(MathHelper.ToRadians(limbParams.Ragdoll.SpritesheetOrientation));
                    var front      = ConvertUnits.ToDisplayUnits(body.FarseerBody.GetWorldPoint(localFront));
                    front.Y = -front.Y;
                    GUI.DrawLine(spriteBatch, bodyDrawPos, front, Color.Yellow, width: 2);
                    GUI.DrawLine(spriteBatch, from, to, Color.Red, width: 1);
                    GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 12, 12), Color.White, true);
                    GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 12, 12), Color.White, true);
                    GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 10, 10), Color.Blue, true);
                    GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 10, 10), Color.Red, true);
                    GUI.DrawRectangle(spriteBatch, new Rectangle((int)front.X, (int)front.Y, 10, 10), Color.Yellow, true);

                    //Vector2 mainLimbFront = ConvertUnits.ToDisplayUnits(ragdoll.MainLimb.body.FarseerBody.GetWorldPoint(ragdoll.MainLimb.body.GetFrontLocal(MathHelper.ToRadians(ragdoll.RagdollParams.SpritesheetOrientation))));
                    //mainLimbFront.Y = -mainLimbFront.Y;
                    //var mainLimbDrawPos = ragdoll.MainLimb.body.DrawPosition;
                    //mainLimbDrawPos.Y = -mainLimbDrawPos.Y;
                    //GUI.DrawLine(spriteBatch, mainLimbDrawPos, mainLimbFront, Color.White, width: 5);
                    //GUI.DrawRectangle(spriteBatch, new Rectangle((int)mainLimbFront.X, (int)mainLimbFront.Y, 10, 10), Color.Yellow, true);
                }
                foreach (var modifier in damageModifiers)
                {
                    float   rotation = -body.TransformedRotation + GetArmorSectorRotationOffset(modifier.ArmorSector) * Dir;
                    Vector2 forward  = VectorExtensions.Forward(rotation);
                    float   size     = ConvertUnits.ToDisplayUnits(body.GetSize().Length() / 2);
                    color = modifier.DamageMultiplier > 1 ? Color.Red : Color.GreenYellow;
                    GUI.DrawLine(spriteBatch, bodyDrawPos, bodyDrawPos + Vector2.Normalize(forward) * size, color, width: (int)Math.Round(4 / cam.Zoom));
                    ShapeExtensions.DrawSector(spriteBatch, bodyDrawPos, size, GetArmorSectorSize(modifier.ArmorSector) * Dir, 40, color, rotation + MathHelper.Pi, thickness: 2 / cam.Zoom);
                }
            }
        }
Пример #9
0
        void UpdateSineAnim(float deltaTime)
        {
            if (CurrentSwimParams == null)
            {
                return;
            }
            movement = TargetMovement;

            if (movement.LengthSquared() > 0.00001f)
            {
                float t = 0.5f;
                if (CurrentSwimParams.RotateTowardsMovement && VectorExtensions.Angle(VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2), movement) > MathHelper.PiOver2)
                {
                    // Reduce the linear movement speed when not facing the movement direction
                    t /= 5;
                }
                Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, t);
            }

            //limbs are disabled when simple physics is enabled, no need to move them
            if (SimplePhysicsEnabled)
            {
                return;
            }
            var mainLimb = MainLimb;

            mainLimb.PullJointEnabled = true;
            //mainLimb.PullJointWorldAnchorB = Collider.SimPosition;

            if (movement.LengthSquared() < 0.00001f)
            {
                WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
                mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
                return;
            }

            Vector2 transformedMovement = reverse ? -movement : movement;
            float   movementAngle       = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
            float   mainLimbAngle       = 0;

            if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
            {
                mainLimbAngle = TorsoAngle.Value;
            }
            else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
            {
                mainLimbAngle = HeadAngle.Value;
            }
            mainLimbAngle *= Dir;
            while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
            {
                movementAngle += MathHelper.TwoPi;
            }
            while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
            {
                movementAngle -= MathHelper.TwoPi;
            }

            if (CurrentSwimParams.RotateTowardsMovement)
            {
                Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
                if (TorsoAngle.HasValue)
                {
                    Limb torso = GetLimb(LimbType.Torso);
                    if (torso != null)
                    {
                        SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
                    }
                }
                if (HeadAngle.HasValue)
                {
                    Limb head = GetLimb(LimbType.Head);
                    if (head != null)
                    {
                        SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
                    }
                }
                if (TailAngle.HasValue)
                {
                    Limb tail = GetLimb(LimbType.Tail);
                    if (tail != null)
                    {
                        float?mainLimbTargetAngle = null;
                        if (mainLimb.type == LimbType.Torso)
                        {
                            mainLimbTargetAngle = TorsoAngle;
                        }
                        else if (mainLimb.type == LimbType.Head)
                        {
                            mainLimbTargetAngle = HeadAngle;
                        }
                        float torque        = TailTorque;
                        float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
                        if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
                        {
                            float diff   = Math.Abs(mainLimb.Rotation - tail.Rotation);
                            float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
                            torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
                        }
                        SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
                    }
                }
            }
            else
            {
                movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
                if (reverse)
                {
                    movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
                }
                if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
                {
                    Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
                }
                else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
                {
                    Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
                }
                if (TorsoAngle.HasValue)
                {
                    Limb torso = GetLimb(LimbType.Torso);
                    torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
                }
                if (HeadAngle.HasValue)
                {
                    Limb head = GetLimb(LimbType.Head);
                    head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
                }
                if (TailAngle.HasValue)
                {
                    Limb tail = GetLimb(LimbType.Tail);
                    tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
                }
            }

            var waveLength    = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
            var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);

            if (waveLength > 0 && waveAmplitude > 0)
            {
                WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
                WalkPos  = MathUtils.WrapAngleTwoPi(WalkPos);
            }

            foreach (var limb in Limbs)
            {
                if (limb.IsSevered)
                {
                    continue;
                }
                if (Math.Abs(limb.Params.ConstantTorque) > 0)
                {
                    limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
                }
                switch (limb.type)
                {
                case LimbType.LeftFoot:
                case LimbType.RightFoot:
                    if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
                    {
                        SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
                    }
                    break;

                case LimbType.Tail:
                    if (waveLength > 0 && waveAmplitude > 0)
                    {
                        float waveRotation = (float)Math.Sin(WalkPos);
                        limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude);
                    }
                    break;
                }
            }

            for (int i = 0; i < Limbs.Length; i++)
            {
                var limb = Limbs[i];
                if (limb.IsSevered)
                {
                    continue;
                }
                if (limb.SteerForce <= 0.0f)
                {
                    continue;
                }
                if (!Collider.PhysEnabled)
                {
                    continue;
                }
                Vector2 pullPos = limb.PullJointWorldAnchorA;
                limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
            }

            Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;

            if (CurrentSwimParams.UseSineMovement)
            {
                mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
                    mainLimb.PullJointWorldAnchorB,
                    Collider.SimPosition,
                    mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
            }
            else
            {
                //mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
                mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
                    mainLimb.PullJointWorldAnchorB,
                    Collider.SimPosition,
                    mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
            }

            floorY = Limbs[0].SimPosition.Y;
        }
Пример #10
0
        protected override void Act(float deltaTime)
        {
            if (followControlledCharacter)
            {
                if (Character.Controlled == null)
                {
                    abandon = true;
                    return;
                }
                Target = Character.Controlled;
            }
            if (Target == character)
            {
                character.AIController.SteeringManager.Reset();
                abandon = true;
                return;
            }
            waitUntilPathUnreachable -= deltaTime;
            if (!character.IsClimbing)
            {
                character.SelectedConstruction = null;
            }
            if (Target is Entity e)
            {
                if (e.Removed)
                {
                    abandon = true;
                }
                else
                {
                    character.AIController.SelectTarget(e.AiTarget);
                }
            }
            bool isInside        = character.CurrentHull != null;
            bool insideSteering  = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
            var  targetHull      = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull;
            bool targetIsOutside = (Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);

            if (isInside && targetIsOutside && !AllowGoingOutside)
            {
                abandon = true;
            }
            else if (waitUntilPathUnreachable < 0)
            {
                if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Unreachable)
                {
                    if (repeat)
                    {
                        SteeringManager.Reset();
                    }
                    else
                    {
                        abandon = true;
                    }
                }
            }
            if (abandon)
            {
#if DEBUG
                DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target.ToString()}", Color.Yellow);
#endif
                if (objectiveManager.CurrentOrder != null)
                {
                    character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
                }
                character.AIController.SteeringManager.Reset();
            }
            else
            {
                Vector2 currTargetSimPos = Vector2.Zero;
                currTargetSimPos = Target.SimPosition;
                // Take the sub position into account in the sim pos
                if (SteeringManager != PathSteering && character.Submarine == null && Target.Submarine != null)
                {
                    currTargetSimPos += Target.Submarine.SimPosition;
                }
                else if (character.Submarine != null && Target.Submarine == null)
                {
                    currTargetSimPos -= character.Submarine.SimPosition;
                }
                else if (character.Submarine != Target.Submarine)
                {
                    if (character.Submarine != null && Target.Submarine != null)
                    {
                        Vector2 diff = character.Submarine.SimPosition - Target.Submarine.SimPosition;
                        currTargetSimPos -= diff;
                    }
                }
                character.AIController.SteeringManager.SteeringSeek(currTargetSimPos);
                if (SteeringManager != PathSteering)
                {
                    SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1, heading: VectorExtensions.Forward(character.AnimController.Collider.Rotation));
                }
                if (getDivingGearIfNeeded)
                {
                    Character followTarget    = Target as Character;
                    bool      needsDivingGear = HumanAIController.NeedsDivingGear(targetHull) || mimic && HumanAIController.HasDivingMask(followTarget);
                    bool      needsDivingSuit = needsDivingGear && (targetHull == null || targetIsOutside || targetHull.WaterPercentage > 90) || mimic && HumanAIController.HasDivingSuit(followTarget);
                    bool      needsEquipment  = false;
                    if (needsDivingSuit)
                    {
                        needsEquipment = !HumanAIController.HasDivingSuit(character);
                    }
                    else if (needsDivingGear)
                    {
                        needsEquipment = !HumanAIController.HasDivingMask(character);
                    }
                    if (needsEquipment)
                    {
                        TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager));
                    }
                }
            }
        }
Пример #11
0
        public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
        {
            if (!(objective.OperateTarget is Gap leak))
            {
                return(true);
            }
            if (leak.Submarine == null)
            {
                return(true);
            }
            Vector2 fromCharacterToLeak = leak.WorldPosition - character.WorldPosition;
            float   dist  = fromCharacterToLeak.Length();
            float   reach = Range + ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);

            //too far away -> consider this done and hope the AI is smart enough to move closer
            if (dist > reach * 2)
            {
                return(true);
            }
            character.AIController.SteeringManager.Reset();
            //steer closer if almost in range
            if (dist > reach)
            {
                if (character.AnimController.InWater)
                {
                    if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
                    {
                        // Swimming inside the sub
                        if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
                        {
                            Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
                            character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
                        }
                        else
                        {
                            character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
                        }
                    }
                    else
                    {
                        // Swimming outside the sub
                        character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
                    }
                }
                else
                {
                    // TODO: use the collider size?
                    if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
                        Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f)
                    {
                        ((HumanoidAnimController)character.AnimController).Crouching = true;
                    }
                    Vector2 standPos = new Vector2(Math.Sign(-fromCharacterToLeak.X), Math.Sign(-fromCharacterToLeak.Y)) / 2;
                    if (leak.IsHorizontal)
                    {
                        standPos.X *= 2;
                        standPos.Y  = 0;
                    }
                    else
                    {
                        standPos.X = 0;
                    }
                    character.AIController.SteeringManager.SteeringSeek(standPos);
                }
            }
            else
            {
                if (dist < reach / 2)
                {
                    // Too close -> steer away
                    character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
                }
                else if (dist <= reach)
                {
                    // In range
                    character.CursorPosition  = leak.Position;
                    character.CursorPosition += VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
                    if (character.AnimController.InWater)
                    {
                        var torso = character.AnimController.GetLimb(LimbType.Torso);
                        // Turn facing the target when not moving (handled in the animcontroller if not moving)
                        Vector2 mousePos    = ConvertUnits.ToSimUnits(character.CursorPosition);
                        Vector2 diff        = (mousePos - torso.SimPosition) * character.AnimController.Dir;
                        float   newRotation = MathUtils.VectorToAngle(diff);
                        character.AnimController.Collider.SmoothRotate(newRotation, 5.0f);

                        if (VectorExtensions.Angle(VectorExtensions.Forward(torso.body.TransformedRotation), fromCharacterToLeak) < MathHelper.PiOver4)
                        {
                            // Swim past
                            Vector2 moveDir = leak.IsHorizontal ? Vector2.UnitY : Vector2.UnitX;
                            moveDir *= character.AnimController.Dir;
                            character.AIController.SteeringManager.SteeringManual(deltaTime, moveDir);
                        }
                    }
                }
            }
            if (item.RequireAimToUse)
            {
                character.SetInput(InputType.Aim, false, true);
                sinTime += deltaTime * 5;
            }
            // Press the trigger only when the tool is approximately facing the target.
            Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
            var     angle          = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);

            if (angle < MathHelper.PiOver4)
            {
                // Check that we don't hit any friendlies
                if (Submarine.PickBodies(item.SimPosition, leak.SimPosition, collisionCategory: Physics.CollisionCharacter).None(hit =>
                {
                    if (hit.UserData is Character c)
                    {
                        if (c == character)
                        {
                            return(false);
                        }
                        return(HumanAIController.IsFriendly(character, c));
                    }
                    return(false);
                }))
                {
                    character.SetInput(InputType.Shoot, false, true);
                    Use(deltaTime, character);
                }
            }

            bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
                             (leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);

            if (leakFixed && leak.FlowTargetHull != null)
            {
                if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
                {
                    character.Speak(TextManager.GetWithVariable("DialogLeaksFixed", "[roomname]", leak.FlowTargetHull.DisplayName, true), null, 0.0f, "leaksfixed", 10.0f);
                }
                else
                {
                    character.Speak(TextManager.GetWithVariable("DialogLeakFixed", "[roomname]", leak.FlowTargetHull.DisplayName, true), null, 0.0f, "leakfixed", 10.0f);
                }
            }

            return(leakFixed);
        }
        protected override void Act(float deltaTime)
        {
            var extinguisherItem = character.Inventory.FindItemByTag("fireextinguisher");

            if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
            {
                TryAddSubObjective(ref getExtinguisherObjective, () =>
                {
                    if (character.IsOnPlayerTeam && !character.HasEquippedItem("fireextinguisher", allowBroken: false))
                    {
                        character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
                    }
                    var getItemObjective = new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
                    {
                        AllowStealing = true,
                        // If the item is inside an unsafe hull, decrease the priority
                        GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
                    };
                    if (objectiveManager.HasOrder <AIObjectiveExtinguishFires>())
                    {
                        getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher"), null, 0.0f, "dialogcannotfindfireextinguisher", 10.0f);
                    }
                    ;
                    return(getItemObjective);
                });
            }
            else
            {
                var extinguisher = extinguisherItem.GetComponent <RepairTool>();
                if (extinguisher == null)
                {
#if DEBUG
                    DebugConsole.ThrowError($"{character.Name}: AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
#endif
                    Abandon = true;
                    return;
                }
                foreach (FireSource fs in targetHull.FireSources)
                {
                    float xDist   = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X) - fs.DamageRange;
                    float yDist   = Math.Abs(character.WorldPosition.Y - fs.WorldPosition.Y);
                    bool  inRange = xDist + yDist < extinguisher.Range;
                    // Use the hull position, because the fire x pos is sometimes inside a wall -> the bot can't ever see it and continues running towards the wall.
                    ISpatialEntity lookTarget = character.CurrentHull == targetHull || character.CurrentHull.linkedTo.Contains(targetHull) ? targetHull : fs as ISpatialEntity;
                    bool           move       = !inRange || !character.CanSeeTarget(lookTarget);
                    if ((inRange && character.CanSeeTarget(lookTarget)) || useExtinquisherTimer > 0)
                    {
                        useExtinquisherTimer += deltaTime;
                        if (useExtinquisherTimer > 2.0f)
                        {
                            useExtinquisherTimer = 0.0f;
                        }
                        // Aim
                        character.CursorPosition = fs.Position;
                        Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
                        float   dist = fromCharacterToFireSource.Length();
                        character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
                        if (extinguisherItem.RequireAimToUse)
                        {
                            character.SetInput(InputType.Aim, false, true);
                            sinTime += deltaTime * 10;
                        }
                        character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
                        extinguisher.Use(deltaTime, character);
                        if (!targetHull.FireSources.Contains(fs))
                        {
                            character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, true), null, 0, "putoutfire", 10.0f);
                        }
                    }
                    if (move)
                    {
                        //go to the first firesource
                        if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: Math.Max(fs.DamageRange, extinguisher.Range * 0.7f))
                        {
                            DialogueIdentifier = "dialogcannotreachfire",
                            TargetName = fs.Hull.DisplayName
                        },
                                               onAbandon: () => Abandon = true,
                                               onCompleted: () => RemoveSubObjective(ref gotoObjective)))
                        {
                            gotoObjective.requiredCondition = () => targetHull == null || character.CanSeeTarget(targetHull);
                        }
                    }
                    else
                    {
                        character.AIController.SteeringManager.Reset();
                    }
                    break;
                }
            }
        }
Пример #13
0
        public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
        {
            Gap leak = objective.OperateTarget as Gap;

            if (leak == null)
            {
                return(true);
            }

            Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
            float   dist           = fromItemToLeak.Length();

            //too far away -> consider this done and hope the AI is smart enough to move closer
            if (dist > Range * 5.0f)
            {
                return(true);
            }

            // TODO: use the collider size?
            if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
                Math.Abs(fromItemToLeak.X) < 100.0f && fromItemToLeak.Y < 0.0f && fromItemToLeak.Y > -150.0f)
            {
                ((HumanoidAnimController)character.AnimController).Crouching = true;
            }

            //steer closer if almost in range
            if (dist > Range)
            {
                Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f);
                standPos = leak.WorldPosition + standPos * Range;
                Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
                character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
            }
            else
            {
                if (dist < Range / 2)
                {
                    // Too close -> steer away
                    character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
                }
                else
                {
                    character.AIController.SteeringManager.Reset();
                }
            }

            sinTime += deltaTime;
            character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
            if (item.RequireAimToUse)
            {
                character.SetInput(InputType.Aim, false, true);
            }

            // Press the trigger only when the tool is approximately facing the target.
            // If the character is climbing, ignore the check, because we cannot aim while climbing.
            if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
            {
                Use(deltaTime, character);
            }
            else
            {
                sinTime -= deltaTime * 2;
            }

            bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
                             (leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);

            if (leakFixed && leak.FlowTargetHull != null)
            {
                sinTime = 0;
                if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
                {
                    character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
                }
                else
                {
                    character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leakfixed", 10.0f);
                }
            }

            return(leakFixed);
        }
        protected override void Act(float deltaTime)
        {
            var extinguisherItem = character.Inventory.FindItemByTag("fireextinguisher");

            if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
            {
                TryAddSubObjective(ref getExtinguisherObjective, () =>
                {
                    character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
                    return(new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
                    {
                        // If the item is inside an unsafe hull, decrease the priority
                        GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
                    });
                });
            }
            else
            {
                var extinguisher = extinguisherItem.GetComponent <RepairTool>();
                if (extinguisher == null)
                {
#if DEBUG
                    DebugConsole.ThrowError($"{character.Name}: AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
#endif
                    Abandon = true;
                    return;
                }
                foreach (FireSource fs in targetHull.FireSources)
                {
                    bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
                    bool move    = !inRange || !HumanAIController.VisibleHulls.Contains(fs.Hull);
                    if (inRange || useExtinquisherTimer > 0.0f)
                    {
                        useExtinquisherTimer += deltaTime;
                        if (useExtinquisherTimer > 2.0f)
                        {
                            useExtinquisherTimer = 0.0f;
                        }
                        // Aim
                        character.CursorPosition = fs.Position;
                        Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
                        float   dist = fromCharacterToFireSource.Length();
                        character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
                        if (extinguisherItem.RequireAimToUse)
                        {
                            bool isOperatingButtons = false;
                            if (SteeringManager == PathSteering)
                            {
                                var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
                                if (door != null && !door.IsOpen && !door.IsBroken)
                                {
                                    isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents <Controller>(true).Any();
                                }
                            }
                            if (!isOperatingButtons)
                            {
                                character.SetInput(InputType.Aim, false, true);
                            }
                            sinTime += deltaTime * 10;
                        }
                        character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
                        extinguisher.Use(deltaTime, character);
                        if (!targetHull.FireSources.Contains(fs))
                        {
                            character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, true), null, 0, "putoutfire", 10.0f);
                        }
                        if (!character.CanSeeTarget(fs))
                        {
                            move = true;
                        }
                    }
                    if (move)
                    {
                        //go to the first firesource
                        if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
                        {
                            DialogueIdentifier = "dialogcannotreachfire",
                            TargetName = fs.Hull.DisplayName
                        },
                                               onAbandon: () => Abandon = true,
                                               onCompleted: () => RemoveSubObjective(ref gotoObjective)))
                        {
                            gotoObjective.requiredCondition = () => HumanAIController.VisibleHulls.Contains(fs.Hull);
                        }
                    }
                    else
                    {
                        character.AIController.SteeringManager.Reset();
                    }
                    break;
                }
            }
        }
Пример #15
0
        private void Attack(float deltaTime)
        {
            character.CursorPosition = Enemy.Position;
            visibilityCheckTimer    -= deltaTime;
            if (visibilityCheckTimer <= 0.0f)
            {
                canSeeTarget         = character.CanSeeTarget(Enemy);
                visibilityCheckTimer = visibilityCheckInterval;
            }
            if (!canSeeTarget)
            {
                return;
            }
            if (Weapon.RequireAimToUse)
            {
                character.SetInput(InputType.Aim, false, true);
            }
            hasAimed = true;
            if (holdFireTimer > 0)
            {
                holdFireTimer -= deltaTime;
                return;
            }
            if (aimTimer > 0)
            {
                aimTimer -= deltaTime;
                return;
            }
            if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 0)
            {
                return;
            }
            if (holdFireCondition != null && holdFireCondition())
            {
                return;
            }
            float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);

            if (!character.IsFacing(Enemy.WorldPosition))
            {
                aimTimer = Rand.Range(1f, 1.5f);
                return;
            }
            if (WeaponComponent is MeleeWeapon meleeWeapon)
            {
                float sqrRange = meleeWeapon.Range * meleeWeapon.Range;
                if (character.AnimController.InWater)
                {
                    if (sqrDist > sqrRange)
                    {
                        return;
                    }
                }
                else
                {
                    // It's possible that the center point of the creature is out of reach, but we could still hit the character.
                    float xDiff = Math.Abs(Enemy.WorldPosition.X - character.WorldPosition.X);
                    if (xDiff > meleeWeapon.Range)
                    {
                        return;
                    }
                    float yDiff = Math.Abs(Enemy.WorldPosition.Y - character.WorldPosition.Y);
                    if (yDiff > Math.Max(meleeWeapon.Range, 100))
                    {
                        return;
                    }
                    if (Enemy.WorldPosition.Y < character.WorldPosition.Y && yDiff > 25)
                    {
                        // The target is probably knocked down? -> try to reach it by crouching.
                        HumanAIController.AnimController.Crouching = true;
                    }
                }
                character.SetInput(InputType.Shoot, false, true);
                Weapon.Use(deltaTime, character);
            }
            else
            {
                if (WeaponComponent is RepairTool repairTool)
                {
                    if (sqrDist > repairTool.Range * repairTool.Range)
                    {
                        return;
                    }
                }
                if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
                {
                    if (myBodies == null)
                    {
                        myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
                    }
                    var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
                    var pickedBody          = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
                    if (pickedBody != null)
                    {
                        Character target = null;
                        if (pickedBody.UserData is Character c)
                        {
                            target = c;
                        }
                        else if (pickedBody.UserData is Limb limb)
                        {
                            target = limb.character;
                        }
                        if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
                        {
                            character.SetInput(InputType.Shoot, false, true);
                            Weapon.Use(deltaTime, character);
                            float reloadTime = 0;
                            if (WeaponComponent is RangedWeapon rangedWeapon)
                            {
                                reloadTime = rangedWeapon.Reload;
                            }
                            if (WeaponComponent is MeleeWeapon mw)
                            {
                                reloadTime = mw.Reload;
                            }
                            aimTimer = reloadTime * Rand.Range(1f, 1.5f);
                        }
                    }
                }
            }
        }