Exemplo n.º 1
0
        public IEnumerable <Status> CreateResources()
        {
            foreach (var status in CreateResource())
            {
                if (status == Status.Fail)
                {
                    yield return(Status.Fail);

                    yield break;
                }
            }

            if (ActualCreatedResource.HasValue(out var res))
            {
                for (var i = 0; i < ItemType.Craft_ResultsCount; ++i)
                {
                    Creature.Inventory.AddResource(res);
                }
                Creature.AI.AddXP((int)ItemType.Craft_BaseCraftTime);
                ActHelper.ApplyWearToTool(Creature.AI, GameSettings.Current.Wear_Craft);
                Des.Finished = true;
                yield return(Status.Success);
            }
            else
            {
                Agent.SetTaskFailureReason("Invalid meta resource");
                yield return(Act.Status.Fail);

                yield break;
            }
        }
Exemplo n.º 2
0
        public override void Initialize()
        {
            var unreserveAct = new Wrap(UnReserve);
            var time         = 3 * (ItemType.BaseCraftTime / Creature.AI.Stats.Intelligence);
            var getResources = new Select(new Domain(() => Des.HasResources || Des.ResourcesReservedFor != null, true),
                                          new Domain(() => !Des.HasResources && (Des.ResourcesReservedFor == Agent || Des.ResourcesReservedFor == null),
                                                     new Sequence(
                                                         new Wrap(ReserveResources),
                                                         new GetResourcesOfApparentType(Agent, RawMaterials)
            {
                BlackboardEntry = "stashed-materials"
            })
                                                     | (new Wrap(UnReserve))
                                                     & false),
                                          new Domain(() => Des.HasResources || Des.ResourcesReservedFor != null, true));

            if (!String.IsNullOrEmpty(ItemType.CraftLocation))
            {
                Tree = new Sequence(
                    new Wrap(() => Creature.FindAndReserve(ItemType.CraftLocation, "craft-location")),
                    new ClearBlackboardData(Agent, "ResourcesStashed"),
                    getResources,
                    new Domain(ResourceStateValid,
                               new Sequence(
                                   ActHelper.CreateEquipmentCheckAct(Agent, "Tool", ActHelper.EquipmentFallback.AllowDefault, "Hammer"),
                                   new GoToTaggedObjectAct(Agent)
                {
                    Teleport             = true,
                    TeleportOffset       = new Vector3(0.5f, 0.0f, 0),
                    ObjectBlackboardName = "craft-location",
                    CheckForOcclusion    = true
                },
                                   new Wrap(() => DestroyResources(() => Agent.Position + MathFunctions.RandVector3Cube() * 0.5f)),
                                   new Wrap(WaitForResources)
                {
                    Name = "Wait for resources."
                },
                                   new Wrap(() => Creature.HitAndWait(true,
                                                                      () => 1.0f,         // Max Progress
                                                                      () => Des.Progress, // Current Progress
                                                                      () =>
                {                                                                         // Increment Progress
                    var location = Creature.AI.Blackboard.GetData <GameComponent>(ItemType.CraftLocation);

                    float workstationBuff = 1.0f;
                    if (location != null)
                    {
                        Creature.Physics.Face(location.Position);
                        if (location.GetComponent <SteamPipes.BuildBuff>().HasValue(out var buff))
                        {
                            workstationBuff = buff.GetBuffMultiplier();
                        }
                    }

                    // Todo: Account for environment buff & 'anvil' buff.

                    Des.Progress += (Creature.Stats.BuildSpeed * workstationBuff) / ItemType.BaseCraftTime;
                },
Exemplo n.º 3
0
        private IEnumerable <Act.Status> PerformOnVoxel(Creature performer, Vector3 pos, KillVoxelTask DigAct, DwarfTime time, float bonus, string faction)
        {
            var tool = ActHelper.GetEquippedItem(performer, "Tool");

            // Play the attack animations.
            Creature.CurrentCharacterMode  = tool.Tool_AttackAnimation;
            Creature.OverrideCharacterMode = true;
            Creature.Sprite.ResetAnimations(Creature.CurrentCharacterMode);
            Creature.Sprite.PlayAnimations(Creature.CurrentCharacterMode);

            while (true)
            {
                if (!DigAct.Voxel.IsValid)
                {
                    performer.AI.SetTaskFailureReason("Failed to dig. Voxel was not valid.");
                    yield return(Act.Status.Fail);

                    yield break;
                }

                Drawer2D.DrawLoadBar(performer.World.Renderer.Camera, DigAct.Voxel.WorldPosition + Vector3.One * 0.5f, Color.White, Color.Black, 32, 1, (float)DigAct.VoxelHealth / DigAct.Voxel.Type.StartingHealth);

                while (!performer.Sprite.AnimPlayer.HasValidAnimation() || performer.Sprite.AnimPlayer.CurrentFrame < tool.Tool_AttackTriggerFrame)
                {
                    if (performer.Sprite.AnimPlayer.HasValidAnimation())
                    {
                        performer.Sprite.AnimPlayer.Play();
                    }
                    yield return(Act.Status.Running);
                }

                DigAct.VoxelHealth -= (tool.Tool_AttackDamage + bonus);
                DigAct.Voxel.Type.HitSound.Play(DigAct.Voxel.WorldPosition);
                if (!String.IsNullOrEmpty(tool.Tool_AttackHitParticles))
                {
                    performer.Manager.World.ParticleManager.Trigger(tool.Tool_AttackHitParticles, DigAct.Voxel.WorldPosition, Color.White, 5);
                }

                if (!String.IsNullOrEmpty(tool.Tool_AttackHitEffect))
                {
                    IndicatorManager.DrawIndicator(Library.CreateSimpleAnimation(tool.Tool_AttackHitEffect), DigAct.Voxel.WorldPosition + Vector3.One * 0.5f,
                                                   10.0f, 1.0f, MathFunctions.RandVector2Circle() * 10, tool.Tool_AttackHitColor, MathFunctions.Rand() > 0.5f);
                }

                yield return(Act.Status.Success);

                yield break;
            }
        }
Exemplo n.º 4
0
 public KillVoxelAct(CreatureAI creature, KillVoxelTask OwnerTask) :
     base(creature)
 {
     Name = "Kill DestinationVoxel " + OwnerTask.Voxel.WorldPosition;
     Tree = new Select(
         new Domain(() => CheckIsDigDesignation(creature, OwnerTask),
                    new Sequence(
                        ActHelper.CreateEquipmentCheckAct(creature, "Tool", ActHelper.EquipmentFallback.AllowDefault, "Pick"),
                        new GoToVoxelAct(OwnerTask.Voxel, PlanAct.PlanType.Radius, creature)
     {
         Radius = 2.0f
     },
                        new DigAct(Agent, OwnerTask))),
         new Wrap(() => Cleanup(creature, OwnerTask)));
 }
Exemplo n.º 5
0
        public ChopEntityAct(GameComponent Entity, CreatureAI Creature) :
            base(Creature)
        {
            this.Entity = Entity;
            Name        = "Harvest Plant";

            Tree = new Domain(Verify(Agent),
                              new Sequence(
                                  ActHelper.CreateEquipmentCheckAct(Creature, "Tool", ActHelper.EquipmentFallback.AllowDefault, "Axe", "Pick"), // Allow hands to be used to harvest as a means to prevent game deadlock.
                                  new GoToEntityAct(Entity, Creature)
            {
                MovingTarget = false,
                PlanType     = PlanAct.PlanType.Adjacent,
                Radius       = 0.0f
            } | new Wrap(() => OnAttackEnd(Creature)),
                                  new AttackAct(Agent, Entity)));
        }
Exemplo n.º 6
0
        public BuildZoneAct(CreatureAI agent, BuildZoneOrder buildRoom) :
            base(agent)
        {
            Name      = "Build BuildRoom " + buildRoom.ToString();
            Resources = buildRoom.ListRequiredResources();
            BuildRoom = buildRoom;
            if (BuildRoom.ResourcesReservedFor != null && BuildRoom.ResourcesReservedFor.IsDead)
            {
                BuildRoom.ResourcesReservedFor = null;
            }

            Tree = new Sequence(
                new Select(
                    new Domain(buildRoom.MeetsBuildRequirements() || buildRoom.ResourcesReservedFor != null,
                               new Always(Status.Success)),
                    new Domain(!buildRoom.MeetsBuildRequirements() && (buildRoom.ResourcesReservedFor == null || buildRoom.ResourcesReservedFor == Agent),
                               new Sequence(
                                   new Wrap(Reserve),
                                   new GetResourcesWithTag(Agent, Resources)
            {
                BlackboardEntry = "zone_resources"
            })),
                    new Domain(buildRoom.MeetsBuildRequirements() || buildRoom.ResourcesReservedFor != null,
                               new Always(Status.Success))),
                new Select(
                    new Domain(() => IsRoomBuildOrder(buildRoom) && !buildRoom.IsBuilt && !buildRoom.IsDestroyed && ValidResourceState(),
                               new Sequence(
                                   ActHelper.CreateEquipmentCheckAct(agent, "Tool", ActHelper.EquipmentFallback.NoFallback, "Hammer"),
                                   SetTargetVoxelFromRoomAct(buildRoom, "ActionVoxel"),
                                   new GoToNamedVoxelAct("ActionVoxel", PlanAct.PlanType.Adjacent, Agent),
                                   new Wrap(PutResources),
                                   new Wrap(WaitForResources)
            {
                Name = "Wait for resources..."
            },
                                   new Wrap(() => Creature.HitAndWait(true, () => 1.0f, () => buildRoom.BuildProgress,
                                                                      () => buildRoom.BuildProgress += agent.Stats.BuildSpeed / buildRoom.VoxelOrders.Count * 0.5f,
                                                                      () => MathFunctions.RandVector3Box(buildRoom.GetBoundingBox()), ContentPaths.Audio.Oscar.sfx_ic_dwarf_craft, () => !buildRoom.IsBuilt && !buildRoom.IsDestroyed))
            {
                Name = "Build room.."
            },
                                   new CreateRoomAct(Agent, buildRoom),
                                   new Wrap(OnFailOrCancel))),
                    new Wrap(OnFailOrCancel))
                );
        }
Exemplo n.º 7
0
        public override void Initialize()
        {
            if (Farm != null)
            {
                if (Farm.Voxel.IsValid)
                {
                    Tree = new Select(new Sequence(
                                          ActHelper.CreateEquipmentCheckAct(Agent, "Tool", ActHelper.EquipmentFallback.NoFallback, "Hoe"),
                                          new GetResourcesOfType(Agent, new List <ResourceTypeAmount> {
                        new ResourceTypeAmount(Farm.SeedType, 1)
                    })
                    {
                        BlackboardEntry = "stashed-resources"
                    },
                                          new Domain(Validate, new GoToVoxelAct(Farm.Voxel, PlanAct.PlanType.Adjacent, Creature.AI)),
                                          new Domain(Validate, new StopAct(Creature.AI)),
                                          new Domain(Validate, new Wrap(FarmATile)),
                                          new Wrap(Cleanup)), new Wrap(Cleanup));
                }
            }

            base.Initialize();
        }
Exemplo n.º 8
0
        public override MaybeNull <Act> CreateScript(Creature creature)
        {
            if (Library.GetVoxelType(VoxType).HasValue(out VoxelType voxtype))
            {
                var resource = creature.World.ListResources().Where(r => Library.GetResourceType(r.Key).HasValue(out var res) && voxtype.CanBuildWith(res)).FirstOrDefault();

                if (resource.Key == null)
                {
                    return(null);
                }

                var resources = new ResourceTypeAmount(resource.Value.Type, 1);

                return(new Select(
                           new Sequence(
                               ActHelper.CreateEquipmentCheckAct(creature.AI, "Tool", ActHelper.EquipmentFallback.NoFallback, "Hammer"),
                               new FailMessage(creature.AI, new GetResourcesOfType(creature.AI, new List <ResourceTypeAmount>()
                {
                    resources
                })
                {
                    BlackboardEntry = "stashed-resource"
                },
                                               "Couldn't locate the correct resources!"),
                               new Domain(() => Validate(creature.AI, Voxel, resources),
                                          new GoToVoxelAct(Voxel, PlanAct.PlanType.Radius, creature.AI, 4.0f)),
                               new PlaceVoxelAct(Voxel, creature.AI, "stashed-resource", VoxType)),
                           new Wrap(creature.RestockAll))
                {
                    Name = "Build Voxel"
                });
            }
            else
            {
                return(null);
            }
        }
Exemplo n.º 9
0
        public IEnumerable <Status> FarmATile()
        {
            if (Farm == null)
            {
                yield return(Status.Fail);

                yield break;
            }
            else if (Farm.Finished)
            {
                yield return(Status.Success);
            }
            else
            {
                Creature.CurrentCharacterMode = Creature.Stats.CurrentClass.AttackMode;
                Creature.Sprite.ResetAnimations(Creature.Stats.CurrentClass.AttackMode);
                Creature.Sprite.PlayAnimations(Creature.Stats.CurrentClass.AttackMode);

                while (Farm.Progress < Farm.TargetProgress && !Farm.Finished)
                {
                    Creature.Physics.Velocity *= 0.1f;
                    Farm.Progress             += 3 * Creature.Stats.BaseFarmSpeed * DwarfTime.Dt;

                    Drawer2D.DrawLoadBar(Agent.Manager.World.Renderer.Camera, Agent.Position + Vector3.Up, Color.LightGreen, Color.Black, 64, 4,
                                         Farm.Progress / Farm.TargetProgress);

                    if (Farm.Progress >= (Farm.TargetProgress * 0.5f) && Farm.Voxel.Type.Name != "TilledSoil" &&
                        Library.GetVoxelType("TilledSoil").HasValue(out VoxelType tilledSoil))
                    {
                        Farm.Voxel.Type = tilledSoil;
                    }

                    if (Farm.Progress >= Farm.TargetProgress && !Farm.Finished)
                    {
                        if (Library.GetResourceType(Farm.SeedType).HasValue(out var seedType))
                        {
                            var plant = EntityFactory.CreateEntity <Plant>(seedType.PlantToGenerate, Farm.Voxel.WorldPosition + new Vector3(0.5f, 1.0f, 0.5f));

                            plant.Farm = Farm;

                            var original = plant.LocalTransform;
                            original.Translation += Vector3.Down;
                            plant.AnimationQueue.Add(new EaseMotion(0.5f, original, plant.LocalTransform.Translation));

                            Creature.Manager.World.ParticleManager.Trigger("puff", original.Translation, Color.White, 20);
                            SoundManager.PlaySound(ContentPaths.Audio.Oscar.sfx_env_plant_grow, Farm.Voxel.WorldPosition, true);
                        }

                        Farm.Finished = true;
                        DestroyResources();
                    }

                    if (MathFunctions.RandEvent(0.01f))
                    {
                        Creature.Manager.World.ParticleManager.Trigger("dirt_particle", Creature.AI.Position, Color.White, 1);
                    }
                    yield return(Status.Running);

                    Creature.Sprite.ReloopAnimations(Creature.Stats.CurrentClass.AttackMode);
                }

                Creature.CurrentCharacterMode = CharacterMode.Idle;
                Creature.AddThought("I farmed something!", new TimeSpan(0, 4, 0, 0), 1.0f);
                Creature.AI.AddXP(1);
                ActHelper.ApplyWearToTool(Creature.AI, GameSettings.Current.Wear_Dig);
                Creature.Sprite.PauseAnimations(Creature.Stats.CurrentClass.AttackMode);
                yield return(Status.Success);
            }
        }
Exemplo n.º 10
0
        public override IEnumerable <Status> Run()
        {
            Creature.Sprite.ResetAnimations(Creature.Stats.CurrentClass.AttackMode);

            // Block since we're in a coroutine.
            while (true)
            {
                var vox = OwnerTask.Voxel;

                // Somehow, there wasn't a voxel to mine.
                if (!vox.IsValid)
                {
                    Creature.DrawIndicator(IndicatorManager.StandardIndicators.Question);
                    Agent.SetTaskFailureReason("Failed to dig. Invalid voxel.");
                    yield return(Act.Status.Fail);

                    break;
                }

                // If the voxel has already been destroyed, just ignore it and return.
                if (OwnerTask.VoxelHealth <= 0 || (CheckOwnership && !Creature.World.PersistentData.Designations.IsVoxelDesignation(vox, DesignationType.Dig)))
                {
                    Creature.CurrentCharacterMode = CharacterMode.Idle;
                    yield return(Act.Status.Success);

                    break;
                }

                // Look at the block and slow your velocity down.
                Creature.Physics.Face(vox.WorldPosition + Vector3.One * 0.5f);
                Creature.Physics.Velocity *= 0.01f;

                // Play the attack animations.
                Creature.CurrentCharacterMode  = Creature.Stats.CurrentClass.AttackMode;
                Creature.OverrideCharacterMode = true;
                Creature.Sprite.ResetAnimations(Creature.CurrentCharacterMode);
                Creature.Sprite.PlayAnimations(Creature.CurrentCharacterMode);

                // Wait until an attack was successful...
                foreach (var status in
                         PerformOnVoxel(Creature,
                                        Creature.Physics.Position,
                                        OwnerTask, DwarfTime.LastTime,
                                        Creature.Stats.BaseDigSpeed,
                                        Creature.Faction.ParentFaction.Name))
                {
                    if (status == Act.Status.Running)
                    {
                        Creature.Physics.Face(vox.WorldPosition + Vector3.One * 0.5f);
                        Creature.Physics.Velocity *= 0.01f;
                        yield return(Act.Status.Running);
                    }
                }

                Creature.OverrideCharacterMode = false;

                // If the voxel has been destroyed by you, gather it.
                if (OwnerTask.VoxelHealth <= 0.0f)
                {
                    if (Library.GetVoxelType(vox.Type.Name).HasValue(out VoxelType voxelType))
                    {
                        Creature.AI.AddXP(Math.Max((int)(voxelType.StartingHealth / 4), 1));
                        Creature.Stats.NumBlocksDestroyed++;
                        ActHelper.ApplyWearToTool(Creature.AI, GameSettings.Current.Wear_Dig);

                        var items = VoxelHelpers.KillVoxel(Creature.World, vox);

                        if (items != null)
                        {
                            foreach (GameComponent item in items)
                            {
                                Creature.Gather(item, TaskPriority.Eventually);
                            }
                        }

                        yield return(Act.Status.Success);
                    }
                }

                // Wait until the animation is done playing before continuing.
                while (!Creature.Sprite.AnimPlayer.IsDone() && Creature.Sprite.AnimPlayer.IsPlaying)
                {
                    Creature.Physics.Face(vox.WorldPosition + Vector3.One * 0.5f);
                    Creature.Physics.Velocity *= 0.01f;
                    yield return(Act.Status.Running);
                }

                // Pause the animation and wait for a recharge timer.
                Creature.Sprite.PauseAnimations(Creature.CurrentCharacterMode);

                Creature.CurrentCharacterMode = CharacterMode.Idle;
                yield return(Act.Status.Running);
            }
        }
Exemplo n.º 11
0
        public override IEnumerable <Status> Run()
        {
            foreach (var res in Agent.Blackboard.GetData <List <Resource> >(ResourceBlackboardName))
            {
                if (!Creature.Inventory.Contains(res))
                {
                    yield return(Status.Fail);
                }
            }

            foreach (var status in Creature.HitAndWait(1.0f, true, () => Location.Coordinate.ToVector3() + Vector3.One * 0.5f))
            {
                if (status == Status.Running)
                {
                    yield return(status);
                }
            }

            foreach (var res in Agent.Blackboard.GetData <List <Resource> >(ResourceBlackboardName))
            {
                var grabbed = Creature.Inventory.RemoveAndCreate(res, Inventory.RestockType.Any);

                if (grabbed == null)
                {
                    yield return(Status.Fail);

                    yield break;
                }
                else
                {
                    // If the creature intersects the box, find a voxel adjacent to it that is free, and jump there to avoid getting crushed.
                    if (Creature.Physics.BoundingBox.Intersects(Location.GetBoundingBox()))
                    {
                        var neighbors = VoxelHelpers.EnumerateAllNeighbors(Location.Coordinate)
                                        .Select(c => new VoxelHandle(Agent.World.ChunkManager, c));

                        var closest     = VoxelHandle.InvalidHandle;
                        var closestDist = float.MaxValue;
                        foreach (var voxel in neighbors)
                        {
                            if (!voxel.IsValid)
                            {
                                continue;
                            }

                            float dist = (voxel.WorldPosition - Creature.Physics.Position).LengthSquared();
                            if (dist < closestDist && voxel.IsEmpty)
                            {
                                closestDist = dist;
                                closest     = voxel;
                            }
                        }

                        if (closest.IsValid)
                        {
                            TossMotion teleport = new TossMotion(0.5f, 1.0f, Creature.Physics.GlobalTransform, closest.WorldPosition + Vector3.One * 0.5f);
                            Creature.Physics.AnimationQueue.Add(teleport);
                        }
                    }

                    // Todo: Shitbox - what happens if the player saves while this animation is in progress?? How is the OnComplete restored?
                    var motion = new TossMotion(1.0f, 2.0f, grabbed.LocalTransform, Location.Coordinate.ToVector3() + new Vector3(0.5f, 0.5f, 0.5f));
                    if (grabbed.GetRoot().GetComponent <Physics>().HasValue(out var grabbedPhysics))
                    {
                        grabbedPhysics.CollideMode = Physics.CollisionMode.None;
                    }
                    grabbed.AnimationQueue.Add(motion);

                    motion.OnComplete += () => grabbed.Die();
                }

                if (Library.GetVoxelType(VoxelType).HasValue(out var vType))
                {
                    PlaceVoxel(Location, vType, Creature.Manager.World);
                }

                Creature.Stats.NumBlocksPlaced++;
                Creature.AI.AddXP(1);
                ActHelper.ApplyWearToTool(Creature.AI, GameSettings.Current.Wear_Craft);

                yield return(Status.Success);

                yield break;
            }
        }
Exemplo n.º 12
0
        public override IEnumerable <Status> Run()
        {
            Creature.IsCloaked = false;

            if (CurrentAttack == null)
            {
                yield return(Status.Fail);

                yield break;
            }

            Timeout.Reset();
            FailTimer.Reset();

            if (Target == null && TargetName != null)
            {
                Target = Agent.Blackboard.GetData <GameComponent>(TargetName);

                if (Target == null)
                {
                    yield return(Status.Fail);

                    yield break;
                }
            }

            // In case we kill it - this tells the inventory who to give the loot to.
            if (Agent.Faction.Race.HasValue(out var race) && race.IsIntelligent && Target.GetRoot().GetComponent <Inventory>().HasValue(out var targetInventory))
            {
                targetInventory.SetLastAttacker(Agent);
            }

            CharacterMode defaultCharachterMode = Creature.AI.Movement.CanFly ? CharacterMode.Flying : CharacterMode.Walking;

            var avoided = false;

            while (true)
            {
                Timeout.Update(DwarfTime.LastTime);
                FailTimer.Update(DwarfTime.LastTime);

                if (FailTimer.HasTriggered)
                {
                    Creature.Physics.Orientation   = Physics.OrientMode.RotateY;
                    Creature.OverrideCharacterMode = false;
                    Creature.CurrentCharacterMode  = defaultCharachterMode;
                    yield return(Status.Fail);

                    yield break;
                }

                if (Timeout.HasTriggered)
                {
                    Creature.Physics.Orientation   = Physics.OrientMode.RotateY;
                    Creature.OverrideCharacterMode = false;
                    Creature.CurrentCharacterMode  = defaultCharachterMode;

                    if (Training)
                    {
                        yield return(Status.Success);
                    }
                    else
                    {
                        yield return(Status.Fail);
                    }

                    yield break;
                }

                if (Target == null || Target.IsDead)
                {
                    Creature.CurrentCharacterMode = defaultCharachterMode;
                    Creature.Physics.Orientation  = Physics.OrientMode.RotateY;
                    yield return(Status.Success);
                }

                // Find the location of the melee target
                Vector3 targetPos = new Vector3(Target.GlobalTransform.Translation.X,
                                                Target.GetBoundingBox().Min.Y,
                                                Target.GlobalTransform.Translation.Z);

                Vector2 diff = new Vector2(targetPos.X, targetPos.Z) - new Vector2(Creature.AI.Position.X, Creature.AI.Position.Z);

                Creature.Physics.Face(targetPos);

                bool  intersectsbounds = Creature.Physics.BoundingBox.Intersects(Target.BoundingBox);
                float dist             = diff.Length();
                // If we are really far from the target, something must have gone wrong.
                if (DefensiveStructure == null && !intersectsbounds && dist > CurrentAttack.Weapon.Range * 4)
                {
                    Creature.Physics.Orientation   = Physics.OrientMode.RotateY;
                    Creature.OverrideCharacterMode = false;
                    Creature.CurrentCharacterMode  = defaultCharachterMode;
                    yield return(Status.Fail);

                    yield break;
                }

                if (DefensiveStructure != null)
                {
                    if (Creature.Hp < LastHp) // Defensive structures have damage reduction.
                    {
                        float damage = LastHp - Creature.Hp;
                        Creature.Heal(Math.Min(5.0f, damage)); // Todo: Make this configurable in the structure.

                        if (DefensiveStructure.GetRoot().GetComponent <Health>().HasValue(out var health))
                        {
                            health.Damage(damage);
                            Drawer2D.DrawLoadBar(health.World.Renderer.Camera, DefensiveStructure.Position, Color.White, Color.Black, 32, 1, health.Hp / health.MaxHealth, 0.1f);
                        }

                        LastHp = Creature.Hp;
                    }

                    if (dist > CurrentAttack.Weapon.Range)
                    {
                        float sqrDist = dist * dist;
                        foreach (var threat in Creature.AI.Faction.Threats)
                        {
                            float threatDist = (threat.AI.Position - Creature.AI.Position).LengthSquared();
                            if (threatDist < sqrDist)
                            {
                                sqrDist = threatDist;
                                Target  = threat.Physics;
                                break;
                            }
                        }
                        dist = (float)Math.Sqrt(sqrDist);
                    }

                    if (dist > CurrentAttack.Weapon.Range * 4)
                    {
                        yield return(Status.Fail);

                        yield break;
                    }

                    if (DefensiveStructure.IsDead)
                    {
                        DefensiveStructure = null;
                    }
                }

                LastHp = Creature.Hp;

                // If we're out of attack range, run toward the target.
                if (DefensiveStructure == null && !Creature.AI.Movement.IsSessile && !intersectsbounds && diff.Length() > CurrentAttack.Weapon.Range)
                {
                    Creature.CurrentCharacterMode = defaultCharachterMode;
                    var greedyPath = new GreedyPathAct(Creature.AI, Target, CurrentAttack.Weapon.Range * 0.75f)
                    {
                        PathLength = 5
                    };
                    greedyPath.Initialize();

                    foreach (Act.Status stat in greedyPath.Run())
                    {
                        if (stat == Act.Status.Running)
                        {
                            yield return(Status.Running);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                // If we have a ranged weapon, try avoiding the target for a few seconds to get within range.
                else if (DefensiveStructure == null && !Creature.AI.Movement.IsSessile && !intersectsbounds && !avoided && (CurrentAttack.Weapon.Mode == Weapon.AttackMode.Ranged && dist < CurrentAttack.Weapon.Range * 0.15f))
                {
                    FailTimer.Reset();
                    foreach (Act.Status stat in AvoidTarget(CurrentAttack.Weapon.Range, 3.0f))
                    {
                        yield return(Status.Running);
                    }
                    avoided = true;
                }
                // Else, stop and attack
                else if ((DefensiveStructure == null && dist < CurrentAttack.Weapon.Range) || (DefensiveStructure != null && dist < CurrentAttack.Weapon.Range * 2.0))
                {
                    // Need line of sight for ranged weapons.
                    if (CurrentAttack.Weapon.Mode == Weapon.AttackMode.Ranged &&
                        VoxelHelpers.DoesRayHitSolidVoxel(Creature.World.ChunkManager, Creature.AI.Position, Target.Position))
                    {
                        yield return(Status.Fail);

                        yield break;
                    }

                    FailTimer.Reset();
                    avoided = false;
                    Creature.Physics.Orientation = Physics.OrientMode.Fixed;
                    Creature.Physics.Velocity    = new Vector3(Creature.Physics.Velocity.X * 0.9f, Creature.Physics.Velocity.Y, Creature.Physics.Velocity.Z * 0.9f);
                    CurrentAttack.RechargeTimer.Reset(CurrentAttack.Weapon.RechargeRate);

                    Creature.Sprite.ResetAnimations(Creature.Stats.CurrentClass.AttackMode);
                    Creature.Sprite.PlayAnimations(Creature.Stats.CurrentClass.AttackMode);
                    Creature.CurrentCharacterMode  = Creature.Stats.CurrentClass.AttackMode;
                    Creature.OverrideCharacterMode = true;
                    var timeout = new Timer(10.0f, true);

                    while (!CurrentAttack.Perform(Creature, Target, DwarfTime.LastTime, Creature.Stats.Strength + Creature.Stats.Size, Creature.AI.Position, Creature.Faction.ParentFaction.Name))
                    {
                        timeout.Update(DwarfTime.LastTime);
                        if (timeout.HasTriggered)
                        {
                            break;
                        }

                        Creature.Physics.Velocity = new Vector3(Creature.Physics.Velocity.X * 0.9f, Creature.Physics.Velocity.Y, Creature.Physics.Velocity.Z * 0.9f);
                        if (Creature.AI.Movement.CanFly)
                        {
                            Creature.Physics.ApplyForce(-Creature.Physics.Gravity * 0.1f, DwarfTime.Dt);
                        }
                        yield return(Status.Running);
                    }

                    Agent.AddXP(2); // Gain XP for attacking.
                    ActHelper.ApplyWearToTool(Creature.AI, GameSettings.Current.Wear_Attack);

                    timeout.Reset();

                    while (!Agent.Creature.Sprite.AnimPlayer.IsDone())
                    {
                        timeout.Update(DwarfTime.LastTime);
                        if (timeout.HasTriggered)
                        {
                            break;
                        }

                        if (Creature.AI.Movement.CanFly)
                        {
                            Creature.Physics.ApplyForce(-Creature.Physics.Gravity * 0.1f, DwarfTime.Dt);
                        }
                        yield return(Status.Running);
                    }

                    if (Target.GetRoot().GetComponent <CreatureAI>().HasValue(out var targetCreature) && Creature.AI.FightOrFlight(targetCreature) == CreatureAI.FightOrFlightResponse.Flee)
                    {
                        yield return(Act.Status.Fail);

                        yield break;
                    }

                    Creature.CurrentCharacterMode = CharacterMode.Attacking;

                    Vector3 dogfightTarget = Vector3.Zero;
                    while (!CurrentAttack.RechargeTimer.HasTriggered && !Target.IsDead)
                    {
                        CurrentAttack.RechargeTimer.Update(DwarfTime.LastTime);
                        if (CurrentAttack.Weapon.Mode == Weapon.AttackMode.Dogfight)
                        {
                            dogfightTarget += MathFunctions.RandVector3Cube() * 0.1f;
                            Vector3 output = Creature.Controller.GetOutput(DwarfTime.Dt, dogfightTarget + Target.Position, Creature.Physics.GlobalTransform.Translation) * 0.9f;
                            Creature.Physics.ApplyForce(output - Creature.Physics.Gravity, DwarfTime.Dt);
                        }
                        else
                        {
                            Creature.Physics.Velocity = Vector3.Zero;
                            if (Creature.AI.Movement.CanFly)
                            {
                                Creature.Physics.ApplyForce(-Creature.Physics.Gravity, DwarfTime.Dt);
                            }
                        }
                        yield return(Status.Running);
                    }

                    Creature.CurrentCharacterMode = defaultCharachterMode;
                    Creature.Physics.Orientation  = Physics.OrientMode.RotateY;

                    if (Target.IsDead)
                    {
                        Target = null;
                        Agent.AddXP(10);
                        Creature.Physics.Face(Creature.Physics.Velocity + Creature.Physics.GlobalTransform.Translation);
                        Creature.Stats.NumThingsKilled++;
                        Creature.AddThought("I killed somehing!", new TimeSpan(0, 8, 0, 0), 1.0f);
                        Creature.Physics.Orientation   = Physics.OrientMode.RotateY;
                        Creature.OverrideCharacterMode = false;
                        Creature.CurrentCharacterMode  = defaultCharachterMode;
                        yield return(Status.Success);

                        break;
                    }
                }

                yield return(Status.Running);
            }
        }