private void IssueMoveOrderToAllShips(IFleetNavigable fleetTgt, float tgtStandoffDistance) { bool isFleetwideMove = true; var shipMoveToOrder = new ShipMoveOrder(_fleet.CurrentOrder.Source, fleetTgt as IShipNavigable, ApSpeedSetting, isFleetwideMove, tgtStandoffDistance); _fleet.Elements.ForAll(e => { var ship = e as ShipItem; //D.Log(ShowDebugLog, "{0} issuing Move order to {1}. Target: {2}, Speed: {3}, StandoffDistance: {4:0.#}.", //Name, ship.DebugName, fleetTgt.DebugName, _apMoveSpeed.GetValueName(), tgtStandoffDistance); ship.CurrentOrder = shipMoveToOrder; }); _fleetData.CurrentHeading = (fleetTgt.Position - Position).normalized; }
/// <summary> /// Refreshes the course. /// </summary> /// <param name="mode">The mode.</param> /// <param name="waypoint">The optional waypoint. When not null, this is always a StationaryLocation detour to avoid an obstacle.</param> /// <exception cref="System.NotImplementedException"></exception> private void RefreshApCourse(CourseRefreshMode mode, IFleetNavigable waypoint = null) { //D.Log(ShowDebugLog, "{0}.RefreshCourse() called. Mode = {1}. CourseCountBefore = {2}.", DebugName, mode.GetValueName(), ApCourse.Count); switch (mode) { case CourseRefreshMode.NewCourse: D.AssertNull(waypoint); // A fleet course is constructed by ConstructCourse D.Error("{0}: Illegal {1}.{2}.", DebugName, typeof(CourseRefreshMode).Name, mode.GetValueName()); break; case CourseRefreshMode.AddWaypoint: D.Assert(waypoint is StationaryLocation); ApCourse.Insert(_currentApCourseIndex, waypoint); // changes Course.Count break; case CourseRefreshMode.ReplaceObstacleDetour: D.Assert(waypoint is StationaryLocation); ApCourse.RemoveAt(_currentApCourseIndex); // changes Course.Count ApCourse.Insert(_currentApCourseIndex, waypoint); // changes Course.Count break; case CourseRefreshMode.RemoveWaypoint: D.Assert(waypoint is StationaryLocation); D.AssertEqual(ApCourse[_currentApCourseIndex], waypoint); bool isRemoved = ApCourse.Remove(waypoint); // changes Course.Count D.Assert(isRemoved); _currentApCourseIndex--; break; case CourseRefreshMode.ClearCourse: D.AssertNull(waypoint); ApCourse.Clear(); break; default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(mode)); } //D.Log(ShowDebugLog, "CourseCountAfter = {0}.", ApCourse.Count); HandleApCourseChanged(); }
private bool TryCheckForObstacleEnrouteTo(IFleetNavigable destination, out IFleetNavigable detour, ref int iterationCount) { D.AssertException(iterationCount++ < 10); detour = null; Vector3 destinationBearing = (destination.Position - Position).normalized; float rayLength = destination.GetObstacleCheckRayLength(Position); Ray ray = new Ray(Position, destinationBearing); RaycastHit hitInfo; if (Physics.Raycast(ray, out hitInfo, rayLength, AvoidableObstacleZoneOnlyLayerMask.value)) { // there is an AvoidableObstacleZone in the way. Warning: hitInfo.transform returns the rigidbody parent since // the obstacleZone trigger collider is static. UNCLEAR if this means it forms a compound collider as this is a raycast var obstacleZoneGo = hitInfo.collider.gameObject; var obstacleZoneHitDistance = hitInfo.distance; IAvoidableObstacle obstacle = obstacleZoneGo.GetSafeFirstInterfaceInParents<IAvoidableObstacle>(excludeSelf: true); if (obstacle == destination) { D.Error("{0} encountered obstacle {1} which is the destination. \nRay length = {2:0.00}, DistanceToHit = {3:0.00}.", DebugName, obstacle.DebugName, rayLength, obstacleZoneHitDistance); } else { //D.Log(ShowDebugLog, "{0} encountered obstacle {1} at {2} when checking approach to {3}. \nRay length = {4:0.#}, DistanceToHit = {5:0.#}.", //Name, obstacle.DebugName, obstacle.Position, destination.DebugName, rayLength, obstacleZoneHitDistance); } if (!TryGenerateDetourAroundObstacle(obstacle, hitInfo, out detour)) { return false; } IFleetNavigable newDetour; if (TryCheckForObstacleEnrouteTo(detour, out newDetour, ref iterationCount)) { D.Log(ShowDebugLog, "{0} found another obstacle on the way to detour {1}.", DebugName, detour.DebugName); detour = newDetour; } return true; } return false; }
private bool TryGenerateDetourAroundObstacle(IAvoidableObstacle obstacle, RaycastHit zoneHitInfo, out IFleetNavigable detour) { detour = GenerateDetourAroundObstacle(obstacle, zoneHitInfo, _fleet.UnitMaxFormationRadius); if (obstacle.IsMobile) { Vector3 detourBearing = (detour.Position - Position).normalized; float reqdTurnAngleToDetour = Vector3.Angle(_fleetData.CurrentFlagshipFacing, detourBearing); if (reqdTurnAngleToDetour < DetourTurnAngleThreshold) { // Note: can't use a distance check here as Fleets don't check for obstacles based on time. // They only check when embarking on a new course leg //D.Log(ShowDebugLog, "{0} has declined to generate a detour around mobile obstacle {1}. Reqd Turn = {2:0.#} degrees.", DebugName, obstacle.DebugName, reqdTurnAngleToDetour); return false; } } return true; }
/// <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, IFleetNavigable 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 -= FsmTargetDeathEventHandler; isSubscribeActionTaken = true; } else if (!isSubscribed) { mortalFsmTgt.deathOneShot += FsmTargetDeathEventHandler; 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; }
//private float CalcApMoveTgtStandoffDistance(IFleetNavigable moveTgt) { // float standoffDistance = Constants.ZeroF; // var baseTgt = moveTgt as AUnitBaseCmdItem; // if (baseTgt != null) { // // move target is a base // if (Owner.IsEnemyOf(baseTgt.Owner)) { // // its an enemy base // standoffDistance = TempGameValues.__MaxBaseWeaponsRangeDistance; // } // } // else { // var fleetTgt = moveTgt as FleetCmdItem; // if (fleetTgt != null) { // // move target is a fleet // if (Owner.IsEnemyOf(fleetTgt.Owner)) { // // its an enemy fleet // standoffDistance = TempGameValues.__MaxFleetWeaponsRangeDistance; // } // } // } // return standoffDistance; //} #endregion void ExecuteMoveOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmMoveTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); D.AssertNotNull(CurrentOrder.Target); _fsmTgt = DetermineApMoveTarget(CurrentOrder); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: true); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: true); __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: true); }
void ExecuteJoinFleetOrder_ExitState() { LogEvent(); bool isUnsubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: false); D.Assert(isUnsubscribed); 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; }
void ExecuteAssumeFormationOrder_ExitState() { LogEvent(); _fsmTgt = null; _orderFailureCause = UnitItemOrderFailureCause.None; }
void ExecuteGuardOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmMoveTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); IGuardable guardableTgt = CurrentOrder.Target as IGuardable; D.AssertNotNull(guardableTgt); // Guardable targets are non-enemy owned Sectors, Systems, Planets, Bases and UCenter D.Assert(guardableTgt.IsGuardingAllowedBy(Owner)); _fsmTgt = guardableTgt as IFleetNavigable; __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 ExecuteJoinFleetOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmMoveTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); var fleetToJoin = CurrentOrder.Target as FleetCmdItem; D.AssertNotNull(fleetToJoin); _fsmTgt = fleetToJoin; bool isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); }
void ExecuteExploreOrder_ExitState() { LogEvent(); bool isUnsubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: false); D.Assert(!isUnsubscribed); // OPTIMIZE IFleetExplorable cannot die isUnsubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: false); D.Assert(isUnsubscribed); isUnsubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: false); D.Assert(isUnsubscribed); _fsmTgt = null; _shipSystemExploreTgtAssignments = null; _orderFailureCause = UnitItemOrderFailureCause.None; }
void ExecuteExploreOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmMoveTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); IFleetExplorable fleetExploreTgt = CurrentOrder.Target as IFleetExplorable; // Fleet explorable targets are non-enemy owned sectors, systems and UCenter D.AssertNotNull(fleetExploreTgt); D.Assert(fleetExploreTgt.IsExploringAllowedBy(Owner)); D.Assert(!fleetExploreTgt.IsFullyExploredBy(Owner)); _fsmTgt = fleetExploreTgt; bool isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.TargetDeath, _fsmTgt, toSubscribe: true); D.Assert(!isSubscribed); // OPTIMIZE IFleetExplorable cannot die isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.InfoAccessChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); isSubscribed = __AttemptFsmTgtSubscriptionChg(FsmTgtEventSubscriptionMode.OwnerChg, _fsmTgt, toSubscribe: true); D.Assert(isSubscribed); }
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; }
void ExecuteAssumeFormationOrder_UponPreconfigureState() { LogEvent(); if (_fsmTgt != null) { D.Error("{0} _fsmMoveTgt {1} should not already be assigned.", DebugName, _fsmTgt.DebugName); } D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); _fsmTgt = CurrentOrder.Target; // No reason to subscribe to _fsmTgt-related events as _fsmTgt is either StationaryLocation or null }
private void __ValidateKnowledgeOfOrderTarget(IFleetNavigable target, FleetDirective directive) { if (directive == FleetDirective.Retreat || directive == FleetDirective.Withdraw || directive == FleetDirective.Disband || directive == FleetDirective.Refit || directive == FleetDirective.Repair || directive == FleetDirective.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 == FleetDirective.AssumeFormation) { D.Assert(target == null || target is StationaryLocation || target is MobileLocation); return; } if (directive == FleetDirective.Scuttle) { D.AssertNull(target); 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); } }
IEnumerator ExecuteAssumeFormationOrder_EnterState() { LogEvent(); if (_fsmTgt != null) { // a LocalAssyStation target was specified so move there together first D.Assert(_fsmTgt is StationaryLocation); _apMoveSpeed = Speed.Standard; _apMoveTgtStandoffDistance = Constants.ZeroF; Call(FleetState.Moving); yield return null; // reqd so Return()s here if (_orderFailureCause != UnitItemOrderFailureCause.None) { switch (_orderFailureCause) { case UnitItemOrderFailureCause.UnitItemNeedsRepair: // TODO Initiate Fleet Repair and communicate failure to boss? break; case UnitItemOrderFailureCause.UnitItemDeath: // TODO Communicate failure to boss? break; case UnitItemOrderFailureCause.TgtDeath: case UnitItemOrderFailureCause.TgtUncatchable: case UnitItemOrderFailureCause.TgtRelationship: case UnitItemOrderFailureCause.TgtUnreachable: case UnitItemOrderFailureCause.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(_orderFailureCause)); } yield return null; } // If there was a failure generated by Moving, resulting new Orders or Dead state should keep this point from being reached D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); _fsmTgt = null; // only used to Move to the target if any } Call(FleetState.AssumingFormation); yield return null; if (_orderFailureCause != UnitItemOrderFailureCause.None) { switch (_orderFailureCause) { case UnitItemOrderFailureCause.UnitItemNeedsRepair: // TODO Initiate Fleet Repair and communicate failure to boss? break; case UnitItemOrderFailureCause.UnitItemDeath: // TODO Communicate failure to boss? break; case UnitItemOrderFailureCause.TgtDeath: case UnitItemOrderFailureCause.TgtRelationship: case UnitItemOrderFailureCause.TgtUncatchable: case UnitItemOrderFailureCause.TgtUnreachable: case UnitItemOrderFailureCause.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(_orderFailureCause)); } yield return null; } // If there was a failure generated by AssumingFormation, resulting new Orders or Dead state should keep this point from being reached D.AssertDefault((int)_orderFailureCause, _orderFailureCause.GetValueName()); CurrentState = FleetState.Idling; }
/// <summary> /// Plots the course to the target and notifies the requester of the outcome via the onCoursePlotSuccess or Failure events. /// </summary> /// <param name="apTgt">The target this AutoPilot is being engaged to reach.</param> /// <param name="apSpeed">The speed the autopilot should travel at.</param> /// <param name="apTgtStandoffDistance">The target standoff distance.</param> internal void PlotPilotCourse(IFleetNavigable apTgt, Speed apSpeed, float apTgtStandoffDistance) { Utility.ValidateNotNull(apTgt); D.Assert(!InvalidApSpeeds.Contains(apSpeed), apSpeed.GetValueName()); ApTarget = apTgt; ApSpeedSetting = apSpeed; _apTgtStandoffDistance = apTgtStandoffDistance; IList<Vector3> directCourse; if (TryDirectCourse(out directCourse)) { // use this direct course //D.Log(ShowDebugLog, "{0} will use a direct course to {1}.", DebugName, ApTarget.DebugName); _isApCourseFromPath = false; ConstructApCourse(directCourse); HandleApCoursePlotSuccess(); } else { _isApCourseFromPath = true; ResetPathReplotValues(); PlotPath(); } }
private void GetApMoveOrderSettings(FleetOrder moveOrder, out IFleetNavigable apMoveTgt, out Speed apMoveSpeed, out float apMoveTgtStandoffDistance) { D.Assert(moveOrder.Directive == FleetDirective.Move || moveOrder.Directive == FleetDirective.FullSpeedMove); // Determine move speed apMoveSpeed = moveOrder.Directive == FleetDirective.FullSpeedMove ? Speed.Full : Speed.Standard; // Determine move target IFleetNavigable moveTgt = null; IFleetNavigable moveOrderTgt = moveOrder.Target; ISystem_Ltd systemMoveTgt = moveOrderTgt as ISystem_Ltd; if (systemMoveTgt != null) { // move target is a system if (Topography == Topography.System) { // fleet is currently in a system ISector_Ltd fleetSector = SectorGrid.Instance.GetSectorContaining(Position); ISystem_Ltd fleetSystem = fleetSector.System; if (fleetSystem == systemMoveTgt) { // move target of a system from inside the same system is the closest assembly station within that system moveTgt = GameUtility.GetClosest(Position, systemMoveTgt.LocalAssemblyStations); } } } else { ISector_Ltd sectorMoveTgt = moveOrderTgt as ISector_Ltd; if (sectorMoveTgt != null) { // target is a sector ISector_Ltd fleetSector = SectorGrid.Instance.GetSectorContaining(Position); if (fleetSector == sectorMoveTgt) { // move target of a sector from inside the same sector is the closest assembly station within that sector moveTgt = GameUtility.GetClosest(Position, sectorMoveTgt.LocalAssemblyStations); } } } if (moveTgt == null) { moveTgt = moveOrderTgt; } apMoveTgt = moveTgt; // Determine move target standoff distance apMoveTgtStandoffDistance = CalcApMoveTgtStandoffDistance(moveOrderTgt); }
/// <summary> /// Checks for an obstacle en-route to the provided <c>destination</c>. Returns true if one /// is found that requires immediate action and provides the detour to avoid it, false otherwise. /// </summary> /// <param name="destination">The current destination. May be the ApTarget or an obstacle detour.</param> /// <param name="detour">The obstacle detour.</param> /// <returns> /// <c>true</c> if an obstacle was found and a detour generated, false if the way is effectively clear. /// </returns> private bool TryCheckForObstacleEnrouteTo(IFleetNavigable destination, out IFleetNavigable detour) { int iterationCount = Constants.Zero; return TryCheckForObstacleEnrouteTo(destination, out detour, ref iterationCount); }
/// <summary> /// Initializes a new instance of the <see cref="FleetOrder" /> class. /// </summary> /// <param name="directive">The order directive.</param> /// <param name="source">The source of this order.</param> /// <param name="target">The target of this order. Default is null.</param> public FleetOrder(FleetDirective directive, OrderSource source, IFleetNavigable target = null) { D.AssertNotEqual(OrderSource.Captain, source); Directive = directive; Source = source; Target = target; }
/// <summary> /// Gets the standoff distance for the provided moveTgt. /// </summary> /// <param name="moveTgt">The move target.</param> /// <returns></returns> private float CalcApMoveTgtStandoffDistance(IFleetNavigable moveTgt) { float standoffDistance = Constants.ZeroF; Player moveTgtOwner; var baseTgt = moveTgt as IUnitBaseCmd_Ltd; if (baseTgt != null) { // move target is a base if (baseTgt.TryGetOwner(Owner, out moveTgtOwner)) { if (Owner.IsEnemyOf(moveTgtOwner)) { // its an enemy base standoffDistance = TempGameValues.__MaxBaseWeaponsRangeDistance; } } } else { var fleetTgt = moveTgt as IFleetCmd_Ltd; if (fleetTgt != null) { // move target is a fleet if (fleetTgt.TryGetOwner(Owner, out moveTgtOwner)) { if (Owner.IsEnemyOf(moveTgtOwner)) { // its an enemy fleet standoffDistance = TempGameValues.__MaxFleetWeaponsRangeDistance; } } } } return standoffDistance; }