public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { switch (objective.Option.ToLowerInvariant()) { case "power up": float tempDiff = load - temperature; shutDownTemp = Math.Min(load + 1000.0f, 7500.0f); //temperature too high/low if (Math.Abs(tempDiff) > 500.0f) { AutoTemp = false; FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff); CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff); } //temperature OK else { AutoTemp = true; } break; case "shutdown": shutDownTemp = 0.0f; break; } return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { #if CLIENT if (GameMain.Client != null) { return(false); } #endif if (objective.Option.ToLowerInvariant() == "stoppumping") { #if SERVER if (FlowPercentage > 0.0f) { item.CreateServerEvent(this); } #endif FlowPercentage = 0.0f; } else { #if SERVER if (!IsActive || FlowPercentage > -100.0f) { item.CreateServerEvent(this); } #endif IsActive = true; FlowPercentage = -100.0f; } return(true); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { #if CLIENT if (GameMain.Client != null) { return(false); } #endif if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase)) { #if SERVER if (objective.Override || FlowPercentage > 0.0f) { item.CreateServerEvent(this); } #endif IsActive = false; FlowPercentage = 0.0f; } else { #if SERVER if (objective.Override || !IsActive || FlowPercentage > -100.0f) { item.CreateServerEvent(this); } #endif IsActive = true; FlowPercentage = -100.0f; } return(true); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (objective.Override) { if (user != character && user != null && user.SelectedConstruction == item) { character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f); } } user = character; if (!AutoPilot) { unsentChanges = true; AutoPilot = true; } switch (objective.Option.ToLowerInvariant()) { case "maintainposition": if (objective.Override) { if (!MaintainPos) { unsentChanges = true; MaintainPos = true; } if (!posToMaintain.HasValue) { unsentChanges = true; posToMaintain = controlledSub != null ? controlledSub.WorldPosition : item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition; } } break; case "navigateback": if (objective.Override) { if (MaintainPos || LevelEndSelected || !LevelStartSelected) { unsentChanges = true; } SetDestinationLevelStart(); } break; case "navigatetodestination": if (objective.Override) { if (MaintainPos || !LevelEndSelected || LevelStartSelected) { unsentChanges = true; } SetDestinationLevelEnd(); } break; } sonar?.AIOperate(deltaTime, character, objective); return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { #if CLIENT if (GameMain.Client != null) { return(false); } #endif if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge") { if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f) { #if SERVER item.CreateServerEvent(this); #endif RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio; #if CLIENT rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); #endif character.Speak(TextManager.GetWithVariables("DialogChargeBatteries", new string[2] { "[itemname]", "[rate]" }, new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() }, new bool[2] { true, false }), null, 1.0f, "chargebattery", 10.0f); } } else { if (rechargeSpeed > 0.0f) { #if SERVER item.CreateServerEvent(this); #endif RechargeSpeed = 0.0f; #if CLIENT rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); #endif character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries", new string[2] { "[itemname]", "[rate]" }, new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() }, new bool[2] { true, false }), null, 1.0f, "chargebattery", 10.0f); } } return(true); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (!IsActive || !aiPingCheckPending) { return(false); } Dictionary <string, List <Character> > targetGroups = new Dictionary <string, List <Character> >(); foreach (Character c in Character.CharacterList) { if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; } if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; } if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) { continue; } string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition); if (!targetGroups.ContainsKey(directionName)) { targetGroups.Add(directionName, new List <Character>()); } targetGroups[directionName].Add(c); } foreach (KeyValuePair <string, List <Character> > targetGroup in targetGroups) { string dialogTag = "DialogSonarTarget"; if (targetGroup.Value.Count > 1) { dialogTag = "DialogSonarTargetMultiple"; } else if (targetGroup.Value[0].Mass > 100.0f) { dialogTag = "DialogSonarTargetLarge"; } character.Speak(TextManager.Get(dialogTag).Replace("[direction]", targetGroup.Key).Replace("[count]", targetGroup.Value.Count.ToString()), null, 0, "sonartarget" + targetGroup.Value[0].ID, 30); //prevent the character from reporting other targets in the group for (int i = 1; i < targetGroup.Value.Count; i++) { character.DisableLine("sonartarget" + targetGroup.Value[i].ID); } } return(true); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { Gap leak = objective.OperateTarget as Gap; if (leak == null) { return(true); } float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition); //too far away -> consider this done and hope the AI is smart enough to move closer if (dist > range * 5.0f) { return(true); } //steer closer if almost in range if (dist > range) { Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(item.WorldPosition.X - leak.WorldPosition.X), 0.0f) : new Vector2(0.0f, Math.Sign(item.WorldPosition.Y - leak.WorldPosition.Y)); standPos = leak.WorldPosition + standPos * range; character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f); } else { //close enough -> stop moving character.AIController.SteeringManager.Reset(); } character.CursorPosition = leak.Position; character.SetInput(InputType.Aim, false, true); Use(deltaTime, character); return(leak.Open <= 0.0f); }
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 override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return(false); } IsActive = true; float degreeOfSuccess = DegreeOfSuccess(character); //characters with insufficient skill levels don't refuel the reactor if (degreeOfSuccess > 0.2f) { if (objective.SubObjectives.None()) { var containedItems = item.ContainedItems; foreach (Item fuelRod in containedItems) { if (fuelRod != null && fuelRod.Condition <= 0.0f) { if (!FindSuitableContainer(character, i => { var container = i.GetComponent <ItemContainer>(); if (container == null) { return(0); } if (container.Inventory.IsFull()) { return(0); } if (container.ShouldBeContained(fuelRod, out bool isRestrictionsDefined)) { if (isRestrictionsDefined) { return(3); } else { if (fuelRod.Prefab.IsContainerPreferred(container, out bool isPreferencesDefined)) { return(isPreferencesDefined ? 2 : 1); } else { return(isPreferencesDefined ? 0 : 1); } } } else { return(0); } }, out Item targetContainer)) { return(false); } var decontainObjective = new AIObjectiveDecontainItem(character, fuelRod, item.GetComponent <ItemContainer>(), objective.objectiveManager, targetContainer?.GetComponent <ItemContainer>()); decontainObjective.Abandoned += () => { itemIndex = 0; if (targetContainer != null) { ignoredContainers.Add(targetContainer); } }; objective.AddSubObjectiveInQueue(decontainObjective); } } } if (aiUpdateTimer > 0.0f) { aiUpdateTimer -= deltaTime; return(false); } //load more fuel if the current maximum output is only 50% of the current load if (NeedMoreFuel(minimumOutputRatio: 0.5f)) { aiUpdateTimer = AIUpdateInterval; if (objective.SubObjectives.None()) { var containFuelObjective = new AIObjectiveContainItem(character, fuelTags, item.GetComponent <ItemContainer>(), objective.objectiveManager) { targetItemCount = item.ContainedItems.Count(i => i != null && fuelTags.Any(t => i.Prefab.Identifier == t || i.HasTag(t))) + 1, GetItemPriority = (Item fuelItem) => { if (fuelItem.ParentInventory?.Owner is Item) { //don't take fuel from other reactors if (((Item)fuelItem.ParentInventory.Owner).GetComponent <Reactor>() != null) { return(0.0f); } } return(1.0f); } }; containFuelObjective.Abandoned += () => objective.Abandon = true; objective.AddSubObjective(containFuelObjective); character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); } return(false); } else if (TooMuchFuel()) { foreach (Item item in item.ContainedItems) { if (item != null && fuelTags.Any(t => item.Prefab.Identifier == t || item.HasTag(t))) { if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots)) { item.Drop(character); } break; } } } } if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item) { character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); } LastUser = lastAIUser = character; bool prevAutoTemp = autoTemp; bool prevShutDown = shutDown; float prevFissionRate = targetFissionRate; float prevTurbineOutput = targetTurbineOutput; switch (objective.Option.ToLowerInvariant()) { case "powerup": shutDown = false; if (objective.Override || !autoTemp) { //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually if (degreeOfSuccess < 0.5f) { AutoTemp = true; } else { AutoTemp = false; UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f); } } #if CLIENT onOffSwitch.BarScroll = 0.0f; fissionRateScrollBar.BarScroll = FissionRate / 100.0f; turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; #endif break; case "shutdown": #if CLIENT onOffSwitch.BarScroll = 1.0f; #endif AutoTemp = false; shutDown = true; targetFissionRate = 0.0f; targetTurbineOutput = 0.0f; break; } if (autoTemp != prevAutoTemp || prevShutDown != shutDown || Math.Abs(prevFissionRate - targetFissionRate) > 1.0f || Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f) { unsentChanges = true; } aiUpdateTimer = AIUpdateInterval; return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return(false); } character.AIController.SteeringManager.Reset(); bool shutDown = objective.Option.Equals("shutdown", StringComparison.OrdinalIgnoreCase); IsActive = true; if (!shutDown) { float degreeOfSuccess = DegreeOfSuccess(character); float refuelLimit = 0.3f; //characters with insufficient skill levels don't refuel the reactor if (degreeOfSuccess > refuelLimit) { if (aiUpdateTimer > 0.0f) { aiUpdateTimer -= deltaTime; return(false); } aiUpdateTimer = AIUpdateInterval; // load more fuel if the current maximum output is only 50% of the current load // or if the fuel rod is (almost) deplenished float minCondition = fuelConsumptionRate * MathUtils.Pow2((degreeOfSuccess - refuelLimit) * 2); if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition)) { bool outOfFuel = false; var container = item.GetComponent <ItemContainer>(); if (objective.SubObjectives.None()) { var containObjective = AIContainItems <Reactor>(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC, dropItemOnDeselected: true); containObjective.Completed += ReportFuelRodCount; containObjective.Abandoned += ReportFuelRodCount; character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); void ReportFuelRodCount() { if (!character.IsOnPlayerTeam) { return; } if (character.Submarine != Submarine.MainSub) { return; } int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("reactorfuel") && i.Condition > 1); if (remainingFuelRods == 0) { character.Speak(TextManager.Get("DialogOutOfFuelRods"), null, 0.0f, "outoffuelrods", 30.0f); outOfFuel = true; } else if (remainingFuelRods < 3) { character.Speak(TextManager.Get("DialogLowOnFuelRods"), null, 0.0f, "lowonfuelrods", 30.0f); } } } return(outOfFuel); } else { if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character)) { if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f) { objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true)); return(false); } else { character.Speak(TextManager.Get("DialogReactorIsBroken"), identifier: "reactorisbroken", minDurationBetweenSimilar: 30.0f); } } if (TooMuchFuel()) { DropFuel(minCondition: 0.1f, maxCondition: 100); } else { DropFuel(minCondition: 0, maxCondition: 0); } } } } if (objective.Override) { if (lastUser != null && lastUser != character && lastUser != LastAIUser) { if (lastUser.SelectedConstruction == item && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); } } } else if (LastUserWasPlayer && lastUser != null && lastUser.TeamID == character.TeamID) { return(true); } LastUser = LastAIUser = character; bool prevAutoTemp = autoTemp; bool prevPowerOn = _powerOn; float prevFissionRate = targetFissionRate; float prevTurbineOutput = targetTurbineOutput; if (shutDown) { PowerOn = false; AutoTemp = false; targetFissionRate = 0.0f; targetTurbineOutput = 0.0f; unsentChanges = true; return(true); } else { PowerOn = true; if (objective.Override || !autoTemp) { //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually if (degreeOfSuccess < 0.5f) { AutoTemp = true; } else { AutoTemp = false; UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f); } } #if CLIENT FissionRateScrollBar.BarScroll = FissionRate / 100.0f; TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; #endif if (autoTemp != prevAutoTemp || prevPowerOn != _powerOn || Math.Abs(prevFissionRate - targetFissionRate) > 1.0f || Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f) { unsentChanges = true; } aiUpdateTimer = AIUpdateInterval; return(false); } void DropFuel(float minCondition, float maxCondition) { if (item.OwnInventory?.AllItems != null) { var container = item.GetComponent <ItemContainer>(); foreach (Item item in item.OwnInventory.AllItemsMod) { if (item.ConditionPercentage <= maxCondition && item.ConditionPercentage >= minCondition) { item.Drop(character); break; } } } } }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { var projectiles = GetLoadedProjectiles(); if (projectiles.Count == 0 || (projectiles.Count == 1 && objective.Option.ToLowerInvariant() != "fire at will")) { ItemContainer container = null; foreach (MapEntity e in item.linkedTo) { var containerItem = e as Item; if (containerItem == null) { continue; } container = containerItem.GetComponent <ItemContainer>(); if (container != null) { break; } } if (container == null || container.ContainableItems.Count == 0) { return(true); } var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Names[0], container); containShellObjective.IgnoreAlreadyContainedItems = true; objective.AddSubObjective(containShellObjective); return(false); } else if (GetAvailablePower() < powerConsumption) { var batteries = item.GetConnectedComponents <PowerContainer>(); float lowestCharge = 0.0f; PowerContainer batteryToLoad = null; foreach (PowerContainer battery in batteries) { if (batteryToLoad == null || battery.Charge < lowestCharge) { batteryToLoad = battery; lowestCharge = battery.Charge; } } if (batteryToLoad == null) { return(true); } if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false)); return(false); } } //enough shells and power Character closestEnemy = null; float closestDist = 3000.0f; foreach (Character enemy in Character.CharacterList) { //ignore humans and characters that are inside the sub if (enemy.IsDead || enemy.SpeciesName == "human" || enemy.AnimController.CurrentHull != null) { continue; } float dist = Vector2.Distance(enemy.WorldPosition, item.WorldPosition); if (dist < closestDist) { closestEnemy = enemy; closestDist = dist; } } if (closestEnemy == null) { return(false); } character.CursorPosition = closestEnemy.WorldPosition; if (item.Submarine != null) { character.CursorPosition -= item.Submarine.Position; } character.SetInput(InputType.Aim, false, true); float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition); float turretAngle = -rotation; if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.01f) { return(false); } var pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), closestEnemy.SimPosition, null); if (pickedBody != null && !(pickedBody.UserData is Limb)) { return(false); } if (objective.Option.ToLowerInvariant() == "fire at will") { Use(deltaTime, character); } return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { RechargeSpeed = maxRechargeSpeed * 0.5f; return(true); }
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); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { character.AIController.SteeringManager.Reset(); if (objective.Override) { if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f); } } user = character; if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character)) { if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f) { objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true)); return(false); } else { character.Speak(TextManager.Get("DialogNavTerminalIsBroken"), identifier: "navterminalisbroken", minDurationBetweenSimilar: 30.0f); } } if (!AutoPilot) { unsentChanges = true; AutoPilot = true; } IncreaseSkillLevel(user, deltaTime); switch (objective.Option.ToLowerInvariant()) { case "maintainposition": if (objective.Override) { SetMaintainPosition(); } break; case "navigateback": if (Level.IsLoadedOutpost) { break; } if (DockingSources.Any(d => d.Docked)) { item.SendSignal("1", "toggle_docking"); } if (objective.Override) { if (MaintainPos || LevelEndSelected || !LevelStartSelected || navigateTactically) { unsentChanges = true; } SetDestinationLevelStart(); } break; case "navigatetodestination": if (Level.IsLoadedOutpost) { break; } if (DockingSources.Any(d => d.Docked)) { item.SendSignal("1", "toggle_docking"); } if (objective.Override) { if (MaintainPos || !LevelEndSelected || LevelStartSelected || navigateTactically) { unsentChanges = true; } SetDestinationLevelEnd(); } break; case "navigatetactical": if (Level.IsLoadedOutpost) { break; } if (DockingSources.Any(d => d.Docked)) { item.SendSignal("1", "toggle_docking"); } if (objective.Override) { if (MaintainPos || LevelEndSelected || LevelStartSelected || !navigateTactically) { unsentChanges = true; } SetDestinationTactical(); } break; } sonar?.AIOperate(deltaTime, character, objective); if (!MaintainPos && showIceSpireWarning && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("dialogicespirespottedsonar"), null, 0.0f, "icespirespottedsonar", 60.0f); } return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return(false); } if (objective.Override) { HasBeenTuned = false; } if (HasBeenTuned) { return(true); } float targetRatio = string.IsNullOrEmpty(objective.Option) || objective.Option.Equals("charge", StringComparison.OrdinalIgnoreCase) ? aiRechargeTargetRatio : -1; if (targetRatio > 0 || float.TryParse(objective.Option, out targetRatio)) { if (Math.Abs(rechargeSpeed - maxRechargeSpeed * targetRatio) > 0.05f) { #if SERVER item.CreateServerEvent(this); #endif RechargeSpeed = maxRechargeSpeed * targetRatio; #if CLIENT if (rechargeSpeedSlider != null) { rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); } #endif if (character.IsOnPlayerTeam) { character.Speak(TextManager.GetWithVariables("DialogChargeBatteries", new string[2] { "[itemname]", "[rate]" }, new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() }, new bool[2] { true, false }), null, 1.0f, "chargebattery", 10.0f); } } } else { if (rechargeSpeed > 0.0f) { #if SERVER item.CreateServerEvent(this); #endif RechargeSpeed = 0.0f; #if CLIENT if (rechargeSpeedSlider != null) { rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); } #endif if (character.IsOnPlayerTeam) { character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries", new string[2] { "[itemname]", "[rate]" }, new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() }, new bool[2] { true, false }), null, 1.0f, "chargebattery", 10.0f); } } } return(true); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return(false); } IsActive = true; float degreeOfSuccess = DegreeOfSuccess(character); //characters with insufficient skill levels don't refuel the reactor if (degreeOfSuccess > 0.2f) { //remove used-up fuel from the reactor var containedItems = item.ContainedItems; foreach (Item item in containedItems) { if (item != null && item.Condition <= 0.0f) { item.Drop(character); } } if (aiUpdateTimer > 0.0f) { aiUpdateTimer -= deltaTime; return(false); } //load more fuel if the current maximum output is only 50% of the current load if (NeedMoreFuel(minimumOutputRatio: 0.5f)) { var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent <ItemContainer>(), objective.objectiveManager) { targetItemCount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1, GetItemPriority = (Item fuelItem) => { if (fuelItem.ParentInventory?.Owner is Item) { //don't take fuel from other reactors if (((Item)fuelItem.ParentInventory.Owner).GetComponent <Reactor>() != null) { return(0.0f); } } return(1.0f); } }; objective.AddSubObjective(containFuelObjective); character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); aiUpdateTimer = AIUpdateInterval; return(false); } else if (TooMuchFuel()) { foreach (Item item in item.ContainedItems) { if (item != null && item.HasTag("reactorfuel")) { if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots)) { item.Drop(character); } break; } } } } if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item) { character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); } LastUser = character; switch (objective.Option.ToLowerInvariant()) { case "powerup": shutDown = false; //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually if (degreeOfSuccess < 0.5f) { if (!autoTemp) { unsentChanges = true; } AutoTemp = true; } else { AutoTemp = false; unsentChanges = true; UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f); } #if CLIENT onOffSwitch.BarScroll = 0.0f; fissionRateScrollBar.BarScroll = FissionRate / 100.0f; turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; #endif break; case "shutdown": #if CLIENT onOffSwitch.BarScroll = 1.0f; #endif if (AutoTemp || !shutDown || targetFissionRate > 0.0f || targetTurbineOutput > 0.0f) { unsentChanges = true; } AutoTemp = false; shutDown = true; targetFissionRate = 0.0f; targetTurbineOutput = 0.0f; break; } aiUpdateTimer = AIUpdateInterval; return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { float degreeOfSuccess = DegreeOfSuccess(character); //characters with insufficient skill levels don't refuel the reactor if (degreeOfSuccess > 0.2f) { //remove used-up fuel from the reactor var containedItems = item.ContainedItems; foreach (Item item in containedItems) { if (item != null && item.Condition <= 0.0f) { item.Drop(); } } //the temperature is too low and not increasing even though the fission rate is high and cooling low // -> we need more fuel if (temperature < load * 0.5f && temperatureChange <= 0.0f && fissionRate > 0.9f && coolingRate < 0.1f) { var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "Fuel Rod", "reactorfuel" }, item.GetComponent <ItemContainer>()); containFuelObjective.MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.NameMatches("Fuel Rod") || i.HasTag("reactorfuel")) + 1; containFuelObjective.GetItemPriority = (Item fuelItem) => { if (fuelItem.ParentInventory?.Owner is Item) { //don't take fuel from other reactors if (((Item)fuelItem.ParentInventory.Owner).GetComponent <Reactor>() != null) { return(0.0f); } } return(1.0f); }; objective.AddSubObjective(containFuelObjective); return(false); } } switch (objective.Option.ToLowerInvariant()) { case "power up": float tempDiff = load - temperature; shutDownTemp = Math.Min(load + 1000.0f, 7500.0f); //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually if (Math.Abs(tempDiff) < 500.0f || degreeOfSuccess < 0.5f) { AutoTemp = true; } else { AutoTemp = false; //higher skill levels make the character adjust the temperature faster FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess; CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess; } break; case "shutdown": shutDownTemp = 0.0f; break; } return(false); }
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); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return(false); } IsActive = true; float degreeOfSuccess = DegreeOfSuccess(character); float refuelLimit = 0.3f; //characters with insufficient skill levels don't refuel the reactor if (degreeOfSuccess > refuelLimit) { if (objective.SubObjectives.None()) { if (!AIDecontainEmptyItems(character, objective, equip: false)) { return(false); } } if (aiUpdateTimer > 0.0f) { aiUpdateTimer -= deltaTime; return(false); } aiUpdateTimer = AIUpdateInterval; // load more fuel if the current maximum output is only 50% of the current load // or if the fuel rod is (almost) deplenished float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2); if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition)) { var container = item.GetComponent <ItemContainer>(); if (objective.SubObjectives.None()) { int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1; AIContainItems <Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC); character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); } return(false); } else if (TooMuchFuel()) { var container = item.GetComponent <ItemContainer>(); var containedItems = item.OwnInventory?.Items; if (containedItems != null) { foreach (Item item in containedItems) { if (item != null && container.ContainableItems.Any(ri => ri.MatchesItem(item))) { if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots)) { item.Drop(character); } break; } } } } } if (objective.Override) { if (lastUser != null && lastUser != character && lastUser != LastAIUser) { if (lastUser.SelectedConstruction == item) { character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); } } } else if (LastUserWasPlayer) { return(true); } LastUser = LastAIUser = character; bool prevAutoTemp = autoTemp; bool prevPowerOn = _powerOn; float prevFissionRate = targetFissionRate; float prevTurbineOutput = targetTurbineOutput; switch (objective.Option.ToLowerInvariant()) { case "powerup": PowerOn = true; if (objective.Override || !autoTemp) { //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually if (degreeOfSuccess < 0.5f) { AutoTemp = true; } else { AutoTemp = false; UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f); } } #if CLIENT FissionRateScrollBar.BarScroll = FissionRate / 100.0f; TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; #endif break; case "shutdown": PowerOn = false; AutoTemp = false; targetFissionRate = 0.0f; targetTurbineOutput = 0.0f; unsentChanges = true; return(true); } if (autoTemp != prevAutoTemp || prevPowerOn != _powerOn || Math.Abs(prevFissionRate - targetFissionRate) > 1.0f || Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f) { unsentChanges = true; } aiUpdateTimer = AIUpdateInterval; return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead) { character?.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 30.0f); character.AIController.SelectTarget(null); } if (GetAvailableBatteryPower() < powerConsumption) { var batteries = item.GetConnectedComponents <PowerContainer>(); float lowestCharge = 0.0f; PowerContainer batteryToLoad = null; foreach (PowerContainer battery in batteries) { if (batteryToLoad == null || battery.Charge < lowestCharge) { batteryToLoad = battery; lowestCharge = battery.Charge; } } if (batteryToLoad == null) { return(true); } if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false)); return(false); } } int usableProjectileCount = 0; int maxProjectileCount = 0; foreach (MapEntity e in item.linkedTo) { if (!(e is Item projectileContainer)) { continue; } var containedItems = projectileContainer.ContainedItems; if (containedItems != null) { var container = projectileContainer.GetComponent <ItemContainer>(); maxProjectileCount += container.Capacity; int projectiles = containedItems.Count(it => it.Condition > 0.0f); usableProjectileCount += projectiles; } } if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase))) { ItemContainer container = null; Item containerItem = null; foreach (MapEntity e in item.linkedTo) { containerItem = e as Item; if (containerItem == null) { continue; } container = containerItem.GetComponent <ItemContainer>(); if (container != null) { break; } } if (container == null || container.ContainableItems.Count == 0) { return(true); } if (objective.SubObjectives.None()) { if (!AIDecontainEmptyItems(character, objective, equip: true, sourceContainer: container)) { return(false); } } if (objective.SubObjectives.None()) { var loadItemsObjective = AIContainItems <Turret>(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true); loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier }; character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f); } return(false); } //enough shells and power Character closestEnemy = null; float closestDist = AIRange * AIRange; foreach (Character enemy in Character.CharacterList) { // Ignore dead, friendly, and those that are inside the same sub if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; } if (HumanAIController.IsFriendly(character, enemy)) { continue; } float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition); if (dist > closestDist) { continue; } float angle = -MathUtils.VectorToAngle(enemy.WorldPosition - item.WorldPosition); float midRotation = (minRotation + maxRotation) / 2.0f; while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; } while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; } if (angle < minRotation || angle > maxRotation) { continue; } closestEnemy = enemy; closestDist = dist; } if (closestEnemy == null) { return(false); } character.AIController.SelectTarget(closestEnemy.AiTarget); character.CursorPosition = closestEnemy.WorldPosition; if (item.Submarine != null) { character.CursorPosition -= item.Submarine.Position; } float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition); float turretAngle = -rotation; if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return(false); } Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition); Vector2 end = ConvertUnits.ToSimUnits(closestEnemy.WorldPosition); if (closestEnemy.Submarine != null) { start -= closestEnemy.Submarine.SimPosition; end -= closestEnemy.Submarine.SimPosition; } var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel; var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true, customPredicate: (Fixture f) => { return(!item.StaticFixtures.Contains(f)); }); if (pickedBody == null) { return(false); } Character targetCharacter = null; if (pickedBody.UserData is Character c) { targetCharacter = c; } else if (pickedBody.UserData is Limb limb) { targetCharacter = limb.character; } if (targetCharacter != null) { if (HumanAIController.IsFriendly(character, targetCharacter)) { // Don't shoot friendly characters return(false); } } else { if (pickedBody.UserData is ISpatialEntity e) { Submarine sub = e.Submarine; if (sub == null) { return(false); } if (sub == Item.Submarine) { return(false); } // Don't shoot non-player submarines, i.e. wrecks or outposts. if (!sub.Info.IsPlayer) { return(false); } // Don't shoot friendly submarines. if (sub.TeamID == Item.Submarine.TeamID) { return(false); } } else { // Hit something else, probably a level wall return(false); } } if (objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase)) { character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f); character.SetInput(InputType.Shoot, true, true); } return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead) { character?.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 30.0f); character.AIController.SelectTarget(null); } if (GetAvailablePower() < powerConsumption) { var batteries = item.GetConnectedComponents <PowerContainer>(); float lowestCharge = 0.0f; PowerContainer batteryToLoad = null; foreach (PowerContainer battery in batteries) { if (batteryToLoad == null || battery.Charge < lowestCharge) { batteryToLoad = battery; lowestCharge = battery.Charge; } } if (batteryToLoad == null) { return(true); } if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false)); return(false); } } int usableProjectileCount = 0; int maxProjectileCount = 0; foreach (MapEntity e in item.linkedTo) { if (!(e is Item projectileContainer)) { continue; } var containedItems = projectileContainer.ContainedItems; if (containedItems != null) { var container = projectileContainer.GetComponent <ItemContainer>(); maxProjectileCount += container.Capacity; int projectiles = containedItems.Count(it => it.Condition > 0.0f); usableProjectileCount += projectiles; } } if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill")) { ItemContainer container = null; Item containerItem = null; foreach (MapEntity e in item.linkedTo) { containerItem = e as Item; if (containerItem == null) { continue; } container = containerItem.GetComponent <ItemContainer>(); if (container != null) { break; } } if (container == null || container.ContainableItems.Count == 0) { return(true); } if (container.Inventory.Items[0] != null && container.Inventory.Items[0].Condition <= 0.0f) { var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container, objective.objectiveManager); objective.AddSubObjective(removeShellObjective); } var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container, objective.objectiveManager); character?.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f); containShellObjective.targetItemCount = usableProjectileCount + 1; containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier }; objective.AddSubObjective(containShellObjective); return(false); } //enough shells and power Character closestEnemy = null; float closestDist = 3000 * 3000; foreach (Character enemy in Character.CharacterList) { // Ignore friendly and those that are inside the sub if (enemy.IsDead || enemy.AnimController.CurrentHull != null || !enemy.Enabled) { continue; } if (HumanAIController.IsFriendly(character, enemy)) { continue; } float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition); if (dist > closestDist) { continue; } float angle = -MathUtils.VectorToAngle(enemy.WorldPosition - item.WorldPosition); float midRotation = (minRotation + maxRotation) / 2.0f; while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; } while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; } if (angle < minRotation || angle > maxRotation) { continue; } closestEnemy = enemy; closestDist = dist; } if (closestEnemy == null) { return(false); } character.AIController.SelectTarget(closestEnemy.AiTarget); character.CursorPosition = closestEnemy.WorldPosition; if (item.Submarine != null) { character.CursorPosition -= item.Submarine.Position; } float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition); float turretAngle = -rotation; if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return(false); } var pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), closestEnemy.SimPosition); if (pickedBody == null) { return(false); } Character target = null; if (pickedBody.UserData is Character c) { target = c; } else if (pickedBody.UserData is Limb limb) { target = limb.character; } if (target == null || HumanAIController.IsFriendly(character, target)) { return(false); } if (objective.Option.ToLowerInvariant() == "fireatwill") { character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f); character.SetInput(InputType.Shoot, true, true); } return(false); }
/// <returns>true if the operation was completed</returns> public virtual bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { return(false); }
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (objective.Override) { if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f); } } user = character; if (!AutoPilot) { unsentChanges = true; AutoPilot = true; } IncreaseSkillLevel(user, deltaTime); switch (objective.Option.ToLowerInvariant()) { case "maintainposition": if (objective.Override) { if (!MaintainPos) { unsentChanges = true; MaintainPos = true; } if (!posToMaintain.HasValue) { unsentChanges = true; posToMaintain = controlledSub != null ? controlledSub.WorldPosition : item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition; } } break; case "navigateback": if (Level.IsLoadedOutpost) { break; } if (DockingSources.Any(d => d.Docked)) { item.SendSignal("1", "toggle_docking"); } if (objective.Override) { if (MaintainPos || LevelEndSelected || !LevelStartSelected) { unsentChanges = true; } SetDestinationLevelStart(); } break; case "navigatetodestination": if (Level.IsLoadedOutpost) { break; } if (DockingSources.Any(d => d.Docked)) { item.SendSignal("1", "toggle_docking"); } if (objective.Override) { if (MaintainPos || !LevelEndSelected || LevelStartSelected) { unsentChanges = true; } SetDestinationLevelEnd(); } break; } sonar?.AIOperate(deltaTime, character, objective); if (!MaintainPos && showIceSpireWarning && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("dialogicespirespottedsonar"), null, 0.0f, "icespirespottedsonar", 60.0f); } return(false); }