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; } }
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; },
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; } }
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))); }
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))); }
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)) ); }
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(); }
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); } }
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); } }
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); } }
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; } }
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); } }