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