/// <summary> /// Initializes a new instance of the <see cref="ShipMoveOrder" /> class. /// </summary> /// <param name="source">The source of the order.</param> /// <param name="target">The move target.</param> /// <param name="speed">The move speed.</param> /// <param name="isFleetwide">if set to <c>true</c> the move should be coordinated as a fleet.</param> /// <param name="targetStandoffDistance">When the ship arrives at the target, this is the distance /// from the target it should strive to achieve.</param> public ShipMoveOrder(OrderSource source, IShipNavigable target, Speed speed, bool isFleetwide, float targetStandoffDistance) : base(ShipDirective.Move, source, false, target) { Utility.ValidateNotNull(target); D.AssertNotDefault((int)speed); Utility.ValidateNotNegative(targetStandoffDistance); Speed = speed; IsFleetwide = isFleetwide; TargetStandoffDistance = targetStandoffDistance; }
public AutoPilotDestinationProxy(IShipNavigable destination, Vector3 destOffset, float innerRadius, float outerRadius) { Utility.ValidateNotNull(destination); Utility.ValidateNotNegative(innerRadius); Utility.ValidateForRange(outerRadius, innerRadius, Mathf.Infinity); // HACK Destination = destination; _destOffset = destOffset; InnerRadius = innerRadius; _innerRadiusSqrd = innerRadius * innerRadius; OuterRadius = outerRadius; _outerRadiusSqrd = outerRadius * outerRadius; ArrivalWindowDepth = outerRadius - innerRadius; }
/// <summary> /// Initializes a new instance of the <see cref="ShipOrder" /> class. /// </summary> /// <param name="directive">The order directive.</param> /// <param name="source">The source of this order.</param> /// <param name="toNotifyCmd">if set to <c>true</c> the ship will notify its Command of the outcome.</param> /// <param name="target">The target of this order. No need for FormationStation. Default is null.</param> public ShipOrder(ShipDirective directive, OrderSource source, bool toNotifyCmd = false, IShipNavigable target = null) { if (directive == ShipDirective.Move) { D.AssertEqual(typeof(ShipMoveOrder), GetType()); D.Assert(!toNotifyCmd); } if (directive.EqualsAnyOf(DirectivesWithNullTarget)) { D.AssertNull(target, ToString()); } Directive = directive; Source = source; ToNotifyCmd = toNotifyCmd; Target = target; }
// 4.22.16: Currently Order is issued only by user or fleet as Captain doesn't know whether ship's formationStation // is inside some local obstacle zone. Once HQ has arrived at the LocalAssyStation (if any), individual ships can // still be a long way off trying to get there, so we need to rely on the AutoPilot to manage speed. void ExecuteAssumeStationOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); _fsmTgt = FormationStation; }
void ExecuteAssumeStationOrder_ExitState() { LogEvent(); _orderFailureCause = UnitItemOrderFailureCause.None; _fsmTgt = null; }
void ExecuteMoveOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); D.Assert(!CurrentOrder.ToNotifyCmd); var currentShipMoveOrder = CurrentOrder as ShipMoveOrder; D.Assert(currentShipMoveOrder != null); _fsmTgt = currentShipMoveOrder.Target; __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: true); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: true); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: true); }
void ExecuteMoveOrder_ExitState() { LogEvent(); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: false); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: false); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: false); _fsmTgt = null; _orderFailureCause = UnitItemOrderFailureCause.None; }
/// <summary> /// Calculates and returns the world space offset reqd by the AutoPilotDestinationProxy wrapping _fsmTgt. /// When combined with the target's position, the result represents the actual location in world space this ship /// is trying to reach. The ship will 'arrive' when it gets within the arrival window of the AutoPilotDestinationProxy. /// <remarks>Figures out what the HQ/Cmd ship's approach vector to the target would be if it headed /// directly for the target when called, and uses that rotation to calculate the desired offset to the /// target for this ship, based off the ship's formation station offset. The result returned can be subsequently /// changed to Vector3.zero using AutoPilotDestinationProxy.ResetOffset() if the ship finds it can't reach this initial /// 'arrival point' due to the target itself being in the way. /// </remarks> /// </summary> /// <returns></returns> private Vector3 CalcFleetwideMoveTargetOffset(IShipNavigable moveTarget) { ShipItem hqShip = Command.HQElement; Quaternion hqShipCurrentRotation = hqShip.transform.rotation; Vector3 hqShipToTargetDirection = (moveTarget.Position - hqShip.Position).normalized; Quaternion hqShipRotationChgReqdToFaceTarget = Quaternion.FromToRotation(hqShip.CurrentHeading, hqShipToTargetDirection); Quaternion hqShipRotationThatFacesTarget = Math3D.AddRotation(hqShipCurrentRotation, hqShipRotationChgReqdToFaceTarget); Vector3 shipLocalFormationOffset = FormationStation.LocalOffset; if (moveTarget is AUnitBaseCmdItem || moveTarget is APlanetoidItem || moveTarget is StarItem || moveTarget is UniverseCenterItem) { // destination is a base, planetoid, star or UCenter so its something we could run into if (shipLocalFormationOffset.z > Constants.ZeroF) { // this ship's formation station is in front of Cmd so the ship will run into destination unless it stops short shipLocalFormationOffset = shipLocalFormationOffset.SetZ(Constants.ZeroF); } } Vector3 shipTargetOffset = Math3D.TransformDirectionMath(hqShipRotationThatFacesTarget, shipLocalFormationOffset); //D.Log(ShowDebugLog, "{0}.CalcFleetModeTargetOffset() called. Target: {1}, LocalOffsetUsed: {2}, WorldSpaceOffsetResult: {3}.", // DebugName, moveTarget.DebugName, shipLocalFormationOffset, shipTargetOffset); return shipTargetOffset; }
private bool __TryValidateRightToAssumeHighOrbit(IShipNavigable moveTgt, out IShipOrbitable highOrbitTgt) { highOrbitTgt = moveTgt as IShipOrbitable; if (highOrbitTgt != null && highOrbitTgt.IsHighOrbitAllowedBy(Owner)) { return true; } return false; }
/// <summary> /// Handles the results of the ship's attempt to execute the provided directive. /// </summary> /// <param name="directive">The directive.</param> /// <param name="ship">The ship.</param> /// <param name="isSuccess">if set to <c>true</c> the directive was successfully completed. May still be ongoing.</param> /// <param name="target">The target. Can be null.</param> /// <param name="failCause">The failure cause if not successful.</param> internal void HandleOrderOutcome(ShipDirective directive, ShipItem ship, bool isSuccess, IShipNavigable target = null, UnitItemOrderFailureCause failCause = UnitItemOrderFailureCause.None) { UponOrderOutcome(directive, ship, isSuccess, target, failCause); }
void AssumingFormation_UponOrderOutcome(ShipDirective directive, ShipItem ship, bool isSuccess, IShipNavigable target, UnitItemOrderFailureCause failCause) { LogEvent(); if (directive != ShipDirective.AssumeStation) { D.Warn("{0} State {1} erroneously received OrderOutcome callback with {2} {3}.", DebugName, CurrentState.GetValueName(), typeof(ShipDirective).Name, directive.GetValueName()); return; } D.AssertNull(target); if (isSuccess) { _fsmShipWaitForOnStationCount--; } else { switch (failCause) { case UnitItemOrderFailureCause.UnitItemNeedsRepair: // Ship will get repaired, but even if it goes to its formationStation to do so // it won't communicate its success back to Cmd since Captain ordered it, not Cmd _fsmShipWaitForOnStationCount--; break; case UnitItemOrderFailureCause.UnitItemDeath: _fsmShipWaitForOnStationCount--; break; case UnitItemOrderFailureCause.TgtDeath: case UnitItemOrderFailureCause.TgtRelationship: case UnitItemOrderFailureCause.TgtUncatchable: case UnitItemOrderFailureCause.TgtUnreachable: case UnitItemOrderFailureCause.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(failCause)); } } if (_fsmShipWaitForOnStationCount == Constants.Zero) { Return(); } }
IEnumerator ExecuteAttackOrder_EnterState() { LogEvent(); TryBreakOrbit(); IUnitAttackable unitAttackTgt = CurrentOrder.Target as IUnitAttackable; string unitAttackTgtName = unitAttackTgt.DebugName; if (!unitAttackTgt.IsOperational) { // if this occurs, it happened in the yield return null delay before EnterState execution D.Warn("{0} was killed before {1} could begin attack. Canceling Attack Order.", unitAttackTgtName, DebugName); CurrentState = ShipState.Idling; yield return null; } // Other unitAttackTgt condition changes (owner, relationship, infoAccess) handled by FleetCmd ShipCombatStance stance = Data.CombatStance; if (stance == ShipCombatStance.Disengage) { if (IsHQ) { D.Warn("{0} as HQ cannot have {1} of {2}. Changing to {3}.", DebugName, typeof(ShipCombatStance).Name, ShipCombatStance.Disengage.GetValueName(), ShipCombatStance.Defensive.GetValueName()); Data.CombatStance = ShipCombatStance.Defensive; } else { if (IsThereNeedForAFormationStationChangeTo(WithdrawPurpose.Disengage)) { D.AssertDefault((int)_fsmDisengagePurpose); _fsmDisengagePurpose = WithdrawPurpose.Disengage; ShipOrder disengageOrder = new ShipOrder(ShipDirective.Disengage, OrderSource.Captain); OverrideCurrentOrder(disengageOrder, retainSuperiorsOrder: false); } else { D.Log(ShowDebugLog, "{0} is already {1}d as the current FormationStation meets that need. Canceling Attack Order.", DebugName, ShipDirective.Disengage.GetValueName()); CurrentState = ShipState.Idling; } yield return null; } } if (stance == ShipCombatStance.Defensive) { D.Log(ShowDebugLog, "{0}'s {1} is {2}. Changing Attack order to AssumeStationAndEntrench.", DebugName, typeof(ShipCombatStance).Name, ShipCombatStance.Defensive.GetValueName()); ShipOrder assumeStationAndEntrenchOrder = new ShipOrder(ShipDirective.AssumeStation, OrderSource.Captain) { FollowonOrder = new ShipOrder(ShipDirective.Entrench, OrderSource.Captain) }; OverrideCurrentOrder(assumeStationAndEntrenchOrder, retainSuperiorsOrder: false); yield return null; } if (unitAttackTgt.IsColdWarAttackByAllowed(Owner)) { // we are not at war with the owner of this Unit as Owner must be accessible WeaponRangeMonitors.ForAll(wrm => wrm.ToEngageColdWarEnemies = true); // IMPROVE weapons will shoot at ANY ColdWar or War enemy in range, even innocent ColdWar bystanders } bool allowLogging = true; IShipAttackable primaryAttackTgt; while (unitAttackTgt.IsOperational) { if (TryPickPrimaryAttackTgt(unitAttackTgt, allowLogging, out primaryAttackTgt)) { D.Log(ShowDebugLog, "{0} picked {1} as primary attack target.", DebugName, primaryAttackTgt.DebugName); // target found within sensor range that it can and wants to attack _fsmTgt = primaryAttackTgt as IShipNavigable; Call(ShipState.Attacking); yield return null; // reqd so Return()s here if (_orderFailureCause != UnitItemOrderFailureCause.None) { Command.HandleOrderOutcome(CurrentOrder.Directive, this, isSuccess: false, target: primaryAttackTgt, failCause: _orderFailureCause); switch (_orderFailureCause) { case UnitItemOrderFailureCause.TgtUncatchable: continue; // pick another primary attack target case UnitItemOrderFailureCause.UnitItemNeedsRepair: InitiateRepair(retainSuperiorsOrderOnRepairCompletion: false); break; case UnitItemOrderFailureCause.UnitItemDeath: // No Cmd notification reqd in this state. Dead state will follow break; case UnitItemOrderFailureCause.TgtDeath: // Should not happen as Attacking does not generate a failure cause when target dies case UnitItemOrderFailureCause.TgtRelationship: case UnitItemOrderFailureCause.TgtUnreachable: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(_orderFailureCause)); } yield return null; } else { D.Assert(!primaryAttackTgt.IsOperational); Command.HandleOrderOutcome(CurrentOrder.Directive, this, isSuccess: true, target: primaryAttackTgt); } _fsmTgt = null; allowLogging = true; } else { // declined to pick first or subsequent primary target if (allowLogging) { D.LogBold(ShowDebugLog, "{0} is staying put as it found no target it chooses to attack associated with UnitTarget {1}.", DebugName, unitAttackTgt.DebugName); // either no operational weapons or no targets in sensor range allowLogging = false; } } yield return null; } if (IsInOrbit) { D.Error("{0} is in orbit around {1} after killing {2}.", DebugName, _itemBeingOrbited.DebugName, unitAttackTgtName); } CurrentState = ShipState.Idling; }
void ExecuteAttackOrder_ExitState() { LogEvent(); WeaponRangeMonitors.ForAll(wrm => wrm.ToEngageColdWarEnemies = false); _fsmTgt = null; _orderFailureCause = UnitItemOrderFailureCause.None; }
void ExecuteAssumeCloseOrbitOrder_ExitState() { LogEvent(); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: false); bool isUnsubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: false); D.Assert(isUnsubscribed); isUnsubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: false); D.Assert(isUnsubscribed); _fsmTgt = null; _orderFailureCause = UnitItemOrderFailureCause.None; }
// 4.22.16: Currently Order is issued only by user or fleet. Once HQ has arrived at the IShipCloseOrbitable target, // individual ships can still be a long way off trying to get there, so we need to rely on the AutoPilot to manage speed. void ExecuteAssumeCloseOrbitOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); D.Assert(!CurrentOrder.ToNotifyCmd); var orbitTgt = CurrentOrder.Target as IShipCloseOrbitable; D.Assert(orbitTgt != null); _fsmTgt = orbitTgt; __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: true); bool isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); }
/// <summary> /// Attempts subscribing or unsubscribing to <c>fsmTgt</c> in the mode provided. /// Returns <c>true</c> if the indicated subscribe action was taken, <c>false</c> if not. /// <remarks>Issues a warning if attempting to create a duplicate subscription.</remarks> /// </summary> /// <param name="subscriptionMode">The subscription mode.</param> /// <param name="fsmTgt">The target used by the State Machine.</param> /// <param name="toSubscribe">if set to <c>true</c> subscribe, otherwise unsubscribe.</param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> protected bool __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode subscriptionMode, IShipNavigable fsmTgt, bool toSubscribe) { Utility.ValidateNotNull(fsmTgt); bool isSubscribeActionTaken = false; bool isDuplicateSubscriptionAttempted = false; IItem_Ltd itemFsmTgt = null; bool isSubscribed = __subscriptionStatusLookup[subscriptionMode]; switch (subscriptionMode) { case FsmTgtEventSubscriptionMode.TargetDeath: var mortalFsmTgt = fsmTgt as IMortalItem_Ltd; if (mortalFsmTgt != null) { if (!toSubscribe) { mortalFsmTgt.deathOneShot -= FsmTgtDeathEventHandler; isSubscribeActionTaken = true; } else if (!isSubscribed) { mortalFsmTgt.deathOneShot += FsmTgtDeathEventHandler; isSubscribeActionTaken = true; } else { isDuplicateSubscriptionAttempted = true; } } break; case FsmTgtEventSubscriptionMode.InfoAccessChg: itemFsmTgt = fsmTgt as IItem_Ltd; if (itemFsmTgt != null) { // fsmTgt can be a StationaryLocation if (!toSubscribe) { itemFsmTgt.infoAccessChgd -= FsmTgtInfoAccessChgdEventHandler; isSubscribeActionTaken = true; } else if (!isSubscribed) { itemFsmTgt.infoAccessChgd += FsmTgtInfoAccessChgdEventHandler; isSubscribeActionTaken = true; } else { isDuplicateSubscriptionAttempted = true; } } break; case FsmTgtEventSubscriptionMode.OwnerChg: itemFsmTgt = fsmTgt as IItem_Ltd; if (itemFsmTgt != null) { // fsmTgt can be a StationaryLocation if (!toSubscribe) { itemFsmTgt.ownerChanged -= FsmTgtOwnerChgdEventHandler; isSubscribeActionTaken = true; } else if (!isSubscribed) { itemFsmTgt.ownerChanged += FsmTgtOwnerChgdEventHandler; isSubscribeActionTaken = true; } else { isDuplicateSubscriptionAttempted = true; } } break; case FsmTgtEventSubscriptionMode.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(subscriptionMode)); } if (isDuplicateSubscriptionAttempted) { D.Warn("{0}: Attempting to subscribe to {1}'s {2} when already subscribed.", DebugName, fsmTgt.DebugName, subscriptionMode.GetValueName()); } if (isSubscribeActionTaken) { __subscriptionStatusLookup[subscriptionMode] = toSubscribe; } return isSubscribeActionTaken; }
void ExecuteExploreOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); D.Assert(CurrentOrder.ToNotifyCmd); var exploreTgt = CurrentOrder.Target as IShipExplorable; D.Assert(exploreTgt != null); // individual ships only explore Planets, Stars and UCenter D.Assert(exploreTgt.IsExploringAllowedBy(Owner)); D.Assert(!exploreTgt.IsFullyExploredBy(Owner)); D.Assert(exploreTgt.IsCloseOrbitAllowedBy(Owner)); _fsmTgt = exploreTgt; __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: true); bool isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); }
void ExecuteAttackOrder_UponOrderOutcome(ShipDirective directive, ShipItem ship, bool isSuccess, IShipNavigable target, UnitItemOrderFailureCause failCause) { LogEvent(); if (directive != ShipDirective.Attack) { D.Warn("{0} State {1} erroneously received OrderOutcome callback with {2} {3}.", DebugName, CurrentState.GetValueName(), typeof(ShipDirective).Name, directive.GetValueName()); return; } // TODO keep track of results to make better resulting decisions about what to do as battle rages // IShipAttackable attackedTgt = target as IShipAttackable; // target can be null if ship failed and didn't have a target: Disengaged... }
/// <summary> /// Assesses whether this ship should attempt to assume close orbit around the provided target. /// </summary> /// <param name="target">The target to assess close orbiting.</param> /// <returns> /// <c>true</c> if the ship should initiate assuming close orbit. /// </returns> private bool AssessWhetherToAssumeCloseOrbitAround(IShipNavigable target) { Utility.ValidateNotNull(target); D.Assert(!IsInCloseOrbit); D.Assert(!_helm.IsPilotEngaged); var closeOrbitableTarget = target as IShipCloseOrbitable; if (closeOrbitableTarget != null) { if (!(closeOrbitableTarget is StarItem) && !(closeOrbitableTarget is SystemItem) && !(closeOrbitableTarget is UniverseCenterItem)) { // filter out objectToOrbit items that generate unnecessary knowledge check warnings // OPTIMIZE D.Assert(OwnerAIMgr.HasKnowledgeOf(closeOrbitableTarget as IItem_Ltd)); // ship very close so should know. UNCLEAR Dead sensors?, sensors w/FleetCmd } if (closeOrbitableTarget.IsCloseOrbitAllowedBy(Owner)) { return true; } } return false; }
private void UponOrderOutcome(ShipDirective directive, ShipItem ship, bool isSuccess, IShipNavigable target = null, UnitItemOrderFailureCause failCause = UnitItemOrderFailureCause.None) { RelayToCurrentState(directive, ship, isSuccess, target, failCause); }
private void __ValidateKnowledgeOfOrderTarget(IShipNavigable target, ShipDirective directive) { if (directive == ShipDirective.Retreat || directive == ShipDirective.Disband || directive == ShipDirective.Refit || directive == ShipDirective.StopAttack) { // directives aren't yet implemented return; } if (target is StarItem || target is SystemItem || target is UniverseCenterItem) { // unnecessary check as all players have knowledge of these targets return; } if (directive == ShipDirective.AssumeStation || directive == ShipDirective.Scuttle || directive == ShipDirective.Repair || directive == ShipDirective.Entrench || directive == ShipDirective.Disengage) { D.AssertNull(target); return; } if (directive == ShipDirective.Move) { if (target is StationaryLocation || target is MobileLocation) { return; } if (target is ISector) { return; // IMPROVE currently PlayerKnowledge does not keep track of Sectors } } if (!OwnerAIMgr.HasKnowledgeOf(target as IItem_Ltd)) { D.Error("{0} received {1} order with Target {2} that {3} has no knowledge of.", DebugName, directive.GetValueName(), target.DebugName, Owner.LeaderName); } }
void ExecuteExploreOrder_UponOrderOutcome(ShipDirective directive, ShipItem ship, bool isSuccess, IShipNavigable target, UnitItemOrderFailureCause failCause) { LogEvent(); if (directive != ShipDirective.Explore) { D.Warn("{0} State {1} erroneously received OrderOutcome callback with {2} {3}.", DebugName, CurrentState.GetValueName(), typeof(ShipDirective).Name, directive.GetValueName()); return; } IShipExplorable shipExploreTgt = target as IShipExplorable; D.AssertNotNull(shipExploreTgt); bool issueFleetRecall = false; if (IsShipExploreTargetPartOfSystem(shipExploreTgt)) { // exploreTgt is a planet or star D.Assert(_shipSystemExploreTgtAssignments.ContainsKey(shipExploreTgt)); if (isSuccess) { HandleSystemTargetExploredOrDead(ship, shipExploreTgt); } else { bool isNewShipAssigned; bool testForAdditionalExploringShips = false; switch (failCause) { case UnitItemOrderFailureCause.TgtRelationship: // exploration failed so recall all ships issueFleetRecall = true; break; case UnitItemOrderFailureCause.TgtDeath: HandleSystemTargetExploredOrDead(ship, shipExploreTgt); // This is effectively counted as a success and will show up during the _EnterState's // continuous test System.IsFullyExplored. As not really a failure, no reason to issue a fleet recall. break; case UnitItemOrderFailureCause.UnitItemNeedsRepair: isNewShipAssigned = HandleShipNoLongerAvailableToExplore(ship, shipExploreTgt); if (!isNewShipAssigned) { if (Elements.Count > 1) { // This is not the last ship in the fleet, but the others aren't available. Since it usually takes // more than one ship to explore a System, the other ships might currently be exploring testForAdditionalExploringShips = true; } else { D.AssertEqual(Constants.One, Elements.Count); // Damaged ship is only one left in fleet and it can't explore so exploration failed issueFleetRecall = true; } } break; case UnitItemOrderFailureCause.UnitItemDeath: isNewShipAssigned = HandleShipNoLongerAvailableToExplore(ship, shipExploreTgt); if (!isNewShipAssigned) { if (Elements.Count > 1) { // >1 as dead ship has not yet been removed from fleet // This is not the last ship in the fleet, but the others aren't available. Since it usually takes // more than one ship to explore a System, the other ships might currently be exploring testForAdditionalExploringShips = true; } else { D.AssertEqual(Constants.One, Elements.Count); // dead ship has not yet been removed from fleet // Do nothing as Unit is about to die } } break; case UnitItemOrderFailureCause.TgtUncatchable: case UnitItemOrderFailureCause.TgtUnreachable: case UnitItemOrderFailureCause.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(failCause)); } if (testForAdditionalExploringShips) { var otherShipsCurrentlyExploring = Elements.Cast<ShipItem>().Except(ship).Where(s => s.IsCurrentOrderDirectiveAnyOf(ShipDirective.Explore)); if (otherShipsCurrentlyExploring.Any()) { // Do nothing as there are other ships currently exploring so exploreTarget will eventually be assigned a ship } else { // There are no remaining ships out exploring -> the exploration attempt has failed so issue recall issueFleetRecall = true; } } } } else { // exploreTgt is UCenter D.Assert(shipExploreTgt is UniverseCenterItem); if (isSuccess) { // exploration of UCenter has successfully completed so issue fleet recall issueFleetRecall = true; } else { bool isNewShipAssigned; switch (failCause) { case UnitItemOrderFailureCause.UnitItemNeedsRepair: isNewShipAssigned = HandleShipNoLongerAvailableToExplore(ship, shipExploreTgt); if (!isNewShipAssigned) { // No more ships are available to finish UCenter explore. Since it only takes one ship // to explore UCenter, the other ships, if any, can't currently be exploring, so no reason to wait for them // to complete their exploration. -> the exploration attempt has failed so issue recall issueFleetRecall = true; } break; case UnitItemOrderFailureCause.UnitItemDeath: isNewShipAssigned = HandleShipNoLongerAvailableToExplore(ship, shipExploreTgt); if (!isNewShipAssigned) { if (Elements.Count > 1) { // >1 as dead ship has not yet been removed from fleet // This is not the last ship in the fleet, but the others aren't available. Since it only takes one ship // to explore UCenter, the other ships can't currently be exploring, so no reason to wait for them // to complete their exploration. -> the exploration attempt has failed so issue recall issueFleetRecall = true; } else { D.AssertEqual(Constants.One, Elements.Count); // dead ship has not yet been removed from fleet // Do nothing as Unit is about to die } } break; case UnitItemOrderFailureCause.TgtDeath: case UnitItemOrderFailureCause.TgtRelationship: case UnitItemOrderFailureCause.TgtUncatchable: case UnitItemOrderFailureCause.TgtUnreachable: case UnitItemOrderFailureCause.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(failCause)); } } } if (issueFleetRecall) { IFleetExplorable fleetExploreTgt = CurrentOrder.Target as IFleetExplorable; var closestLocalAssyStation = GameUtility.GetClosest(Position, fleetExploreTgt.LocalAssemblyStations); CurrentOrder = new FleetOrder(FleetDirective.AssumeFormation, OrderSource.CmdStaff, closestLocalAssyStation); } }