View Issue Details

IDProjectCategoryLast Update
0022300AI War 2SuggestionDec 3, 2019 4:38 pm
ReporterStarKelp Assigned ToChris_McElligottPark  
Status resolvedResolutionfixed 
Product Version1.010 Extracting Those Archives 
Fixed in Version1.011 SuperCat Swats Back 
Summary0022300: Lances reworked to go their full distance
DescriptionMade some tweaks to the Lance code to make them hit all targets in a line again.

Specifically, if they hit less than their max target count, they will go their full distance visually. If they hit their max target count, the beam will stop at the last target hit.

Compared to the current implementation, FramePartTimings reported an increase in speed of around 12-15% in a large test fight, linked below, after about a dozen attempts with both Vanilla and Modified code.

Lines modified: 1436-1508

Video: https://www.youtube.com/watch?v=19ZighJtDWg&feature=youtu.be
TagsNo tags attached.

Activities

StarKelp

Dec 3, 2019 4:08 pm

developer   ~0054780

Re uploaded the file, as it appears to have popped out of existence in the initial post.
EntitySimLogicImplementation.cs (101,236 bytes)   
using Arcen.Universal;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Arcen.AIW2.Core;
using System.Linq;

namespace Arcen.AIW2.External
{
    /// <summary>
    /// All of the things here are run on a secondary thread, 
    /// from in the World_AIW2.Instance.DoWorldStepLogic tree,
    /// in the last secondary-thread step of the sim before SimPlannerImplementation 
    /// (on the main thread) can flip to the next sim-frame.
    /// 
    /// This gets run once per entity.
    /// </summary>
    public class EntitySimLogicImplementation : EntitySimLogic
    {
        public EntitySimLogicImplementation()
        {
            EntitySimLogic.Instance = this;
        }
        
        private bool _trace;
        private readonly ArcenCharacterBuffer traceBuffer = new ArcenCharacterBuffer();

        #region ReevaluateUnitOrders
        public override void ReevaluateUnitOrders( ArcenSimContext Context, GameEntity_Squad Entity )
        {
            if ( World.Instance.IsPaused )
                return; //don't waste my time, buddy!
            if ( Entity.Planet != null && Entity.Planet.BattleStatus == PlanetBattleStatus.Tier1_PlayerLookingAtMe )
            {
                //only run the sim cycle groups on the main planet; otherwise we may miss sim cycles entirely
                if ( World_AIW2.CurrentSimCycleSlow != Entity.SimCycleGroup_Slow )
                    return; //catch you next time!
            }

            _trace = Engine_AIW2.TraceAtAll && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && Entity == GameEntity_Base.CurrentlyHoveredOver;
            if ( _trace ) traceBuffer.Clear();
            if ( _trace ) traceBuffer.Add( "ReevaluateUnitOrders: " ).Add( Entity.TypeData.InternalName ).Add( " " ).Add( Entity.PrimaryKeyID );

            GameEntity_Squad guarded = Entity.Guarding.GetSquad();
            if ( guarded != null && guarded.PlanetFaction.Faction.Type != Entity.PlanetFaction.Faction.Type )
            {
                guarded = null;
                Entity.GuardingOffsets.Clear();
                Entity.Guarding.PrimaryKeyID = 0;
                Entity.Guarding.Ref = null;
            }

            EntityOrder order = Entity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision();

            EntityBehaviorType effectiveBehavior = Entity.GetEffectiveOrders().Behavior;

            if ( effectiveBehavior != EntityBehaviorType.Attacker_PursueOnlyInRange )
            {
                if ( order != null && order.ShouldOverrideBehavior )
                {
                    if ( _trace )
                    {
                        if(order.TypeData.Type == EntityOrderType.Wormhole)
                        {
                            traceBuffer.Add("Obeying order type " + order.TypeData.Type + " to " + World_AIW2.Instance.GetPlanetByIndex(order.RelatedPlanetIndex).Name + " which overrides the current behaviour");
                        }
                        else
                            traceBuffer.Add("Obeying order type " + order.TypeData.Type + " which overrides the current behaviour");

                        ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow );
                        traceBuffer.Clear();
                    }
                    return;
                }

                //we rely on this to block us from accidentally ordering around things that are already given orders by humans.
                //that way we can use ClearSource.YesClearHumanOrders, below
                if ( order != null && order.Source == OrderSource.HumanPlayer )
                {
                    if ( _trace )
                    {
                        traceBuffer.Add("Obeying order from player which overrides behaviour");
                        ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow );
                        traceBuffer.Clear();
                    }
                    return;
                }
            }

            if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Entity.GetEffectiveOrders().Behavior=" ).Add( effectiveBehavior.ToString() );

            switch ( effectiveBehavior )
            {
                case EntityBehaviorType.Guard_FleetShip:
                case EntityBehaviorType.Guard_Guardian_Patrolling:
                case EntityBehaviorType.Guard_Guardian_Anchored:
                    {
                        bool isFreeingFromAggro = false;
                        GameEntity_Squad guardSquad = Entity.Guarding.GetSquad();
                        if ( guardSquad != null )
                        {
                            if ( guardSquad.LastTimeTakenDamageFromPlayer > 0 )
                            {
                                //aggro me because guard was aggro'd
                                if ( Entity.LastTimeTakenDamageFromPlayer < guardSquad.LastTimeTakenDamageFromPlayer )
                                    Entity.LastTimeTakenDamageFromPlayer = guardSquad.LastTimeTakenDamageFromPlayer;
                                isFreeingFromAggro = true;
                            }
                            else if ( Entity.LastTimeTakenDamageFromPlayer > 0 )
                            {
                                //aggro guard because I was aggro'd
                                if ( Entity.LastTimeTakenDamageFromPlayer > guardSquad.LastTimeTakenDamageFromPlayer )
                                    guardSquad.LastTimeTakenDamageFromPlayer = Entity.LastTimeTakenDamageFromPlayer;
                                isFreeingFromAggro = true;
                            }
                        }
                        else if ( Entity.LastTimeTakenDamageFromPlayer > 0 )
                            isFreeingFromAggro = true;

                        if ( isFreeingFromAggro )
                        {
                            Entity.Orders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
                            Entity.GuardingOffsets.Clear();
                            Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, -1 );
                            effectiveBehavior = EntityBehaviorType.Attacker_Full;
                        }
                    }
                    break;
            }

            switch ( effectiveBehavior )
            {
                case EntityBehaviorType.Guard_FleetShip:
                    if ( Entity.TypeData.CannotBeStoredInsideGuardPost )
                    {
                        effectiveBehavior = EntityBehaviorType.Attacker_Full;
                        if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because CannotBeStoredInsideGuardPost" );
                    }
                    else
                    {
                        if ( Entity.PlanetFaction.Faction.Type == FactionType.Player )
                        {
                            if ( Entity.PlanetFaction.DataByStance[FactionStance.Hostile].TotalStrengthIncludingNonMilitary > 0 )
                            {
                                effectiveBehavior = EntityBehaviorType.Attacker_Full;
                                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because player-side and Hostile is present" );
                            }
                        }
                        else
                        {
                            ShortRangePlanning_StrengthData_PlanetFaction_Stance hostileData = Entity.PlanetFaction.DataByStance[FactionStance.Hostile];
                            if ( hostileData.SecondsSinceHadAnyStrength >= 0 &&
                                 hostileData.SecondsSinceHadAnyStrength < ExternalConstants.Instance.ResidualAlertSeconds )
                            {
                                effectiveBehavior = EntityBehaviorType.Attacker_Full;
                                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because non-player-side and player is present (" ).Add( hostileData.SecondsSinceHadAnyStrength ).Add( " seconds ago, residual alert for " ).Add( ExternalConstants.Instance.ResidualAlertSeconds ).Add( " seconds)" );
                            }
                        }
                    }
                    break;
                case EntityBehaviorType.Guard_Guardian_Patrolling:
                    {
                        ShortRangePlanning_StrengthData_PlanetFaction_Stance hostileData = Entity.PlanetFaction.DataByStance[FactionStance.Hostile];
                        int secondsSinceLastHostilePresence = hostileData.SecondsSinceHadAnyStrength;
                        if ( secondsSinceLastHostilePresence >= 0 &&
                             secondsSinceLastHostilePresence < ExternalConstants.Instance.ResidualAlertSeconds )
                        {
                            bool isFreeing = false;
                            int freeingAainstFactionIndex = -1;
                            if ( Entity.HullPointsLost > 0 || Entity.ShieldPointsLost > 0 )
                                isFreeing = true;
                            else
                            {
                                // if we need a more sophisticated "am I near any enemies" check than Entity, (for instance, better limiting on the range), then we should probably find some way to offload the bulk of Entity to a planning thread
                                DelegateHelper_CheckForGuardFreeingProvocation_guardPoint = Entity.WorldLocation;
                                DelegateHelper_CheckForGuardFreeingProvocation_foundHit = null;
                                DelegateHelper_GuardAggroDistance = AIWar2GalaxySettingTable.GetIsIntValueFromSettingByName_DuringGame( "GuardAggroDistance" );
                                for ( int i = 0; i < Entity.Planet.Factions.Count; i++ )
                                {
                                    PlanetFaction faction = Entity.Planet.Factions[i];
                                    if ( !Entity.PlanetFaction.GetIsHostileTowards( faction ) )
                                        continue;
                                    faction.Entities.DoForEntities( DelegateHelper_CheckForGuardFreeingProvocation );
                                    if ( DelegateHelper_CheckForGuardFreeingProvocation_foundHit != null )
                                    {
                                        isFreeing = true;
                                        freeingAainstFactionIndex = DelegateHelper_CheckForGuardFreeingProvocation_foundHit.PlanetFaction.Faction.FactionIndex;
                                        break;
                                    }
                                }
                                DelegateHelper_CheckForGuardFreeingProvocation_foundHit = null;
                            }
                            if ( isFreeing )
                            {
                                effectiveBehavior = EntityBehaviorType.Attacker_Full;
                                //if ( hostileData.MobileStrength > FInt.Zero )
                                {
                                    Entity.Orders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
                                    Entity.GuardingOffsets.Clear();
                                    Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, freeingAainstFactionIndex );
                                }
                            }
                        }
                    }
                    break;
            }

            if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "effectiveBehavior=" ).Add( effectiveBehavior.ToString() );

            switch ( effectiveBehavior )
            {
                case EntityBehaviorType.Attacker_Full:
                    this.AttackerLogic( Context, Entity, order, guarded, false );
                    break;
                case EntityBehaviorType.Attacker_PursueOnlyInRange:
                    this.AttackerLogic( Context, Entity, order, guarded, true );
                    break;
                //case EntityBehaviorType.RallyToPlayerFleet:
                //    this.FleetRallyLogic( Context, Entity, order );
                //    break;
                case EntityBehaviorType.Guard_FleetShip:
                    this.Guard_FleetShipLogic( Context, Entity, order, guarded );
                    break;
                case EntityBehaviorType.Guard_Guardian_Patrolling:
                    this.Guard_Guardian_PatrollingLogic( Context, Entity, order, guarded );
                    break;
                case EntityBehaviorType.Guard_Guardian_Anchored:
                    this.Guard_Guardian_AnchoredLogic( Context, Entity, order, guarded );
                    break;
            }

            if ( _trace )
            {
                ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow );
                traceBuffer.Clear();
            }
        }
        #endregion

        #region AttackerLogic
        private void AttackerLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool AllowOverridingHumanOrders )
        {
            if ( guarded != null && Entity.GetMatches( EntityRollupType.ReinforcementLocations ) )
            {
                // if we're a guardian, guarding something, but we're in the Attacker mode, then we should cut loose the rest of the way and become real threat
                Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
                guarded = null;
                Entity.GuardingOffsets.Clear();
                Entity.Guarding.PrimaryKeyID = 0;
                Entity.Guarding.Ref = null;
                return; // next time around we'll revisit the question of what really to do 
            }

            if ( Entity.GetMatches(EntityRollupType.MobileFleetFlagships) && !Entity.FleetMembership.Fleet.IsFleetFlagshipAllowedToUseMovementModes )
                 return; //flagships are only allowed to use this logic if the user has requested it

            if ( Entity.GetMatches( EntityRollupType.HasAnyMetalFlows ) &&
                 Entity.DataForMark.AssistRange > 0 &&
                 (!Entity.TypeData.IsCombatant || Entity.TypeData.IsCombatantDespiteNoWeapons ) )
            {
                this.AttackerLogic_Assister( Context, Entity, order, guarded );
            }
            else
            {
                this.AttackerLogic_Combat( Context, Entity, order, guarded, AllowOverridingHumanOrders );
            }
        }
        #endregion

        #region AttackerLogic_Assister
        private void AttackerLogic_Assister( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
        {
            bool needNewOrder = order == null || order.TypeData.Type != EntityOrderType.Move_Normal;
            //we rely on this to block us from accidentally ordering around things that are already given orders by humans.
            //that way we can use ClearSource.YesClearHumanOrders, below
            if ( order != null && order.Source == OrderSource.HumanPlayer )
                return;
            if ( !needNewOrder )
            {
                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, already moving" );
                return;
            }
            int allowedDistance = ( Entity.DataForMark.AssistRange * 9 ) / 10;
            GameEntity_Squad bestTarget = null;
            int bestDistance = 0;
            for ( int i = 0; i < Entity.FramePlan_PlannedFlows.Count; i++ )
            {
                PlannedMetalFlow flow = Entity.FramePlan_PlannedFlows[i];
                switch ( flow.Purpose )
                {
                    case MetalFlowPurpose.ClaimingNeutrals:
                    case MetalFlowPurpose.RepairingHullsOfFriendlies:
                    case MetalFlowPurpose.RepairingShieldsOfFriendlies:
                    case MetalFlowPurpose.RepairingEnginesOfFriendlies:
                    case MetalFlowPurpose.RebuildingRemains:
                    case MetalFlowPurpose.AssistConstruction:
                        break;
                    default:
                        continue;
                }
                if ( flow.SquadRecipients.Count <= 0 )
                    continue;
                GameEntity_Squad target = flow.SquadRecipients[0];
                int distance;
                if ( Entity.GetIsWithinRangeOf( target, allowedDistance, out distance ) )
                {
                    needNewOrder = false;
                    break;
                }
                if ( bestTarget != null && bestDistance <= distance )
                    continue;
                bestTarget = target;
                bestDistance = distance;
            }
            if ( !needNewOrder )
            {
                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, already in range of an assist target" );
                return;
            }

            ArcenPoint targetPoint;

            if ( bestTarget == null )
            {
                if ( Entity.GuardingOffsets.Count <= 0 )
                {
                    if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, no eligible assist targets and no remembered guard point" );
                    return;
                }
                targetPoint = Entity.GuardingOffsets[0];
                if ( Entity.GetIsWithinRangeOf( targetPoint, allowedDistance ) )
                {
                    if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, no eligible assist targets and within range of remembered guard point" );
                    return;
                }
            }
            else
            {
                AngleDegrees angleToTarget = Entity.WorldLocation.GetAngleToDegrees( bestTarget.WorldLocation );
                int closeToWithinRange = ( Entity.DataForMark.AssistRange * 8 ) / 10;
                targetPoint = Entity.WorldLocation.GetPointAtAngleAndDistance( angleToTarget, closeToWithinRange );
            }

            if ( targetPoint == ArcenPoint.ZeroZeroPoint )
                return; //this would be an invalid order

            Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
            EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other );

            Entity.Orders.QueueOrder( Entity, newOrder );
            if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "picked assist target" ).Add( bestTarget.TypeData.InternalName );
        }
        #endregion

        #region AttackerLogic_Combat
        private void AttackerLogic_Combat( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool AllowOverridingHumanOrders )
        {
            bool needNewOrder = order == null || order.TypeData.Type != EntityOrderType.Attack;
            bool insertOrderInsteadOfClear = false;
            //we rely on this to block us from accidentally ordering around things that are already given orders by humans.
            //that way we can use ClearSource.YesClearHumanOrders, below
            if ( !AllowOverridingHumanOrders )
            {
                if ( order != null && order.Source == OrderSource.HumanPlayer )
                    return;
            }
            else
            {
                insertOrderInsteadOfClear = true;
                if ( order != null && order.Source == OrderSource.HumanPlayer )
                {
                    //can't override these
                    switch ( order.TypeData.Type )
                    {
                        case EntityOrderType.Wormhole:
                        case EntityOrderType.Attack:
                            return;
                        default:
                            break;
                    }
                }
            }

            //Entity.DebugText = DateTime.Now + " needNewOrder: " + needNewOrder;
            #region If we have an existing order, think about changing it based on the FRD stuf
            if ( !needNewOrder && !insertOrderInsteadOfClear )
            {
                GameEntity_Squad existingTarget = order.RelatedSquad.GetSquad();
                if ( existingTarget == null || existingTarget.CalculateShouldBeClearedFromTargetingOfAttacker( Entity ) )
                {
                    //this existing target is invalid!  Get rid of that order
                    needNewOrder = true;
                    Entity.Orders.QueuedOrders.Remove( order );
                    order.ReturnToPool( Entity );
                    order = null;
                }
                else
                {
                    //Entity.DebugText += "  existingTarget: " + existingTarget.TypeData.InternalName;
                }
            }

            if ( !needNewOrder )
            {
                if ( order.Source == OrderSource.HumanPlayer )
                {
                    //if it was given by the player specifically, then don't override it!
                }
                else
                {
                    bool foundAMatch = false;
                    EntitySystem system;
                    for ( int i = 0; i < Entity.Systems.Count; i++ )
                    {
                        system = Entity.Systems[i];
                        if ( system.CurrentFRDTarget.PrimaryKeyID == order.RelatedSquad.PrimaryKeyID )
                            foundAMatch = true;
                    }
                    //none of our systems are after the current FRD target anymore!
                    if ( !foundAMatch )
                        needNewOrder = true;
                }
            }
            #endregion
            if ( !needNewOrder )
            {
                //Entity.DebugText += "  finalNotNeedNewOrder";
                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed" );
                return;
            }

            //since we might have multiple systems that want to do FRD chasing, choose the best one from the list
            GameEntity_Squad bestTarget = null;
            GameEntity_Squad workingTarget = null;
            int bestTargetPriority = 0;
            {
                EntitySystem system;
                for ( int i = 0; i < Entity.Systems.Count; i++ )
                {
                    system = Entity.Systems[i];
                    if ( system.CurrentFRDTarget.PrimaryKeyID > 0 )
                    {
                        if ( Entity.DataForMark.HasAnySniperRangedWeapons )
                        {
                            if ( !system.TypeData.FiresFromAnyRange && system.DataForMark.BaseRange < 999999 )
                                continue; //if the ship has any sniper-ranged systems, then ONLY those systems can do FRD targeting
                        }

                        workingTarget = system.CurrentFRDTarget.GetSquad();
                        if ( workingTarget == null || workingTarget.GetHasBeenDestroyed() || workingTarget.HasBeenRemovedFromSim ||
                            workingTarget.SecondsSpentAsRemains > 0 || workingTarget.ToBeRemovedAtEndOfThisFrame )
                        {
                            //target not valid!  Stop tracking it now
                            system.CurrentFRDTarget.SetInternalRef( null );
                            system.CurrentFRDPriority = 0;
                            system.TimeFRDTargetExpires = 0;
                        }
                        else
                        {
                            if ( system.CurrentFRDPriority > bestTargetPriority || bestTarget  == null )
                            {
                                bestTarget = workingTarget;
                                bestTargetPriority = system.CurrentFRDPriority;
                            }
                        }
                    }
                }
            }
            if ( bestTarget == null )
            {
                //Entity.DebugText += "  bestTarget null!";
                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no bestTarget found!" );
                return;
            }
            //Entity.DebugText += "  bestTarget: " + bestTarget.TypeData.InternalName;

            if ( !insertOrderInsteadOfClear )
                Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
            EntityOrder newOrder = EntityOrder.Create_Attack( Entity, bestTarget.PrimaryKeyID, false, OrderSource.Other );
            newOrder.CalculateStrengthCountingData( Entity, true, true, true );

            if ( insertOrderInsteadOfClear )
                Entity.Orders.InsertOrderAtStart( Entity, newOrder );
            else
                Entity.Orders.QueueOrder( Entity, newOrder );
            if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "picked target" ).Add( bestTarget.TypeData.InternalName );
        }
        #endregion
        
        #region FleetRallyLogic
        //private void FleetRallyLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order )
        //{
        //    if ( Entity.FleetMembership.Fleet.Centerpiece.PrimaryKeyID == Entity.PrimaryKeyID )
        //    {
        //        Entity.Orders.ClearOrders( ClearBehavior.RallyOrdersOnly, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
        //        Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
        //        if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "would be rallying to self since I am centerpiece, cancelling rally" );
        //        return;
        //    }
        //    //Entity only happens with new ships, so skipping checking against itself is good.
        //    GameEntity_Squad entityToRallyTo = Entity.FleetMembership.Fleet.Centerpiece.GetSquad();
        //    if ( entityToRallyTo == null )
        //    {
        //        Entity.Orders.ClearOrders( ClearBehavior.RallyOrdersOnly, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
        //        Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
        //        if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no centerpiece in fleet, cancelling rally" );
        //        return;
        //    }
        //    if ( entityToRallyTo.Planet != Entity.Planet )
        //    {
        //        //the rally leader is on a different planet
        //        bool alreadyHaveWormholeOrderPath = false;
        //        for ( int i = 0; i < Entity.Orders.QueuedOrders.Count; i++ )
        //        {
        //            EntityOrder queuedOrder = Entity.Orders.QueuedOrders[i];
        //            if ( queuedOrder.TypeData.Type != EntityOrderType.Wormhole )
        //                break;
        //            if ( queuedOrder.RelatedPlanetIndex == entityToRallyTo.Planet.PlanetIndex )
        //            {
        //                alreadyHaveWormholeOrderPath = true;
        //                break;
        //            }
        //        }
        //        if ( alreadyHaveWormholeOrderPath )
        //        {
        //            if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "not on same planet, but moving to that planet. Carry On." );
        //        }
        //        else
        //        {
        //            List<Planet> path = World_AIW2.Instance.GetLocalPlayerFaction().FindPath( Entity.Planet, entityToRallyTo.Planet, Context );
        //            if ( path.Count <= 0 )
        //            {
        //                Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
        //                Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
        //                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "not on same planet, and cannot find path to that planet, cancelling rally" );
        //            }
        //            else
        //            {
        //                Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
        //                for ( int i = 0; i < path.Count; i++ )
        //                {
        //                    Planet pathPlanet = path[i];
        //                    EntityOrder newOrder = EntityOrder.Create_Wormhole( Entity, pathPlanet.PlanetIndex, false, false, OrderSource.Other );
        //                    Entity.Orders.QueueOrder( Entity, newOrder );
        //                }
        //                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "not on same planet, so giving wormhole path to that planet of " + Entity.Orders.QueuedOrders.Count + " hops" );
        //            }
        //        }
        //    }
        //    else
        //    {
        //        int distance = Entity.GetDistanceTo_VeryCheapButExtremelyRough( entityToRallyTo, false );
        //        int threshold = ( Entity.DataForMark.Radius + Entity.DataForMark.Radius + entityToRallyTo.DataForMark.Radius );
        //        if ( distance <= threshold )
        //        {
        //            Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
        //            Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
        //            if ( entityToRallyTo.GetEffectiveOrders().Behavior != EntityBehaviorType.RallyToPlayerFleet )
        //            {
        //                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "in range of the primary entity in control group " + entityToRallyTo.TypeData.InternalName + " " + entityToRallyTo.PrimaryKeyID + ", cancelling rally and copying its orders to me" );
        //                entityToRallyTo.Orders.CopyTo( Entity, Entity.Orders );
        //            }
        //            else
        //                //Entity condition can be hit if every unit in the control group died except the reinforcements being rallied to the original control group
        //                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "in range of the primary entity in control group " + entityToRallyTo.TypeData.InternalName + " " + entityToRallyTo.PrimaryKeyID + ", but that unit is also control-group-rallying, so don't copy orders since otherwise we go in a loop" );
        //        }
        //        else
        //        {
        //            if ( order != null && order.TypeData.Type == EntityOrderType.Move && entityToRallyTo.GetDistanceTo_VeryCheapButExtremelyRough( order.RelatedPoint, false ) <= threshold )
        //            {
        //                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "on same planet, out of range, but moving to a point that is in range. Carry On." );
        //            }
        //            else
        //            {
        //                Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
        //                EntityOrder newOrder = EntityOrder.Create_Move( Entity, entityToRallyTo.WorldLocation, false, OrderSource.Other );
        //                Entity.Orders.QueueOrder( Entity, newOrder );
        //                if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "on same planet, out of range, so giving move order to the rally leader's position." );
        //            }
        //        }
        //    }
        //}
        #endregion

        #region Guard_FleetShipLogic
        private void Guard_FleetShipLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
        {
            if ( guarded == null ||
                 guarded.ToBeRemovedAtEndOfThisFrame ||
                 guarded.HasBeenRemovedFromSim ||
                 guarded.Planet != Entity.Planet )
            {
                if ( !Entity.TypeData.CannotBeStoredInsideGuardPost && Entity.GetMatches( EntityRollupType.Drone ) )
                {
                    if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: dying because my guarded-object is gone and I'm a drone" );
                    Entity.Die( Context, true );
                }
                else
                {
                    if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: clearing orders and going Attacker because my guarded-object is gone" );
                    Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
                    int bestFactionIndex = Entity.PlanetFaction.GetIndexOfMostAnnoyingFaction( Context, true );
                    Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, bestFactionIndex );
                }
            }
            else
            {
                if ( Entity.GetIsWithinRangeOf_VeryBasicCheckOnly( guarded.WorldLocation, ExternalConstants.Instance.GuardReabsorptionDistance ) )
                {
                    if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: absorbing because I'm close enough to my guarded-object" );
                    guarded.AddToAIReinforcementPointContents( Entity.TypeData, 1 + Entity.ExtraStackedSquadsInThis );
                    Entity.ExtraStackedSquadsInThis = 0; //without this, it will just remove one from the stack and exponentially get more because of it!
                    Entity.ToBeRemovedAtEndOfThisFrame = true;
                }
                else
                {
                    bool needNewOrder = false;

                    if ( order == null )
                    {
                        if ( _trace )
                        {
                            traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: need new order because no current order" )
                                .Add( " (currently " ).Add( Entity.GetDistanceTo_VeryCheapButExtremelyRough( guarded, false ) ).Add( " distance from guarded object " )
                                .Add( guarded.TypeData.InternalName ).Add( " #" ).Add( guarded.PrimaryKeyID )
                                .Add( ")" );
                        }
                        needNewOrder = true;
                    }
                    else if ( order.TypeData.Type != EntityOrderType.Move_Normal )
                    {
                        if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: need new order because current order is " ).Add( order.TypeData.Type.ToString() ).Add( " instead of Move" );
                    }
                    else if ( !guarded.GetIsWithinRangeOf_VeryBasicCheckOnly( order.RelatedPoint, ExternalConstants.Instance.GuardReabsorptionDistance ) )
                    {
                        if ( _trace )
                            traceBuffer.Add( Environment.NewLine )
                                .Add( "Guard_FleetShip: need new order because current order point (to " )
                                .Add( order.RelatedPoint.ToString() )
                                .Add( ") is too far from my guarded-object (" )
                                .Add( guarded.WorldLocation.ToString() )
                                .Add( "); my current location is (" )
                                .Add( Entity.WorldLocation.ToString() )
                                .Add( ")" )
                                ;
                        needNewOrder = true;
                    }
                    if ( needNewOrder )
                    {
                        if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: clearing orders and issuing new move order to get closer to my guarded-object" );
                        Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
                        EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, guarded.WorldLocation, false, OrderSource.Other );
                        Entity.Orders.QueueOrder( Entity, newOrder );
                    }
                    else
                    {
                        if ( _trace )
                            traceBuffer.Add( Environment.NewLine )
                                .Add( "Guard_FleetShip: doing nothing because I already have a move order (to " )
                                .Add( order.RelatedPoint.ToString() )
                                .Add( ") which is close enough to my guarded-object (" )
                                .Add( guarded.WorldLocation.ToString() )
                                .Add( "); my current location is (" )
                                .Add( Entity.WorldLocation.ToString() )
                                .Add( ")" )
                                ;
                    }
                }
            }
        }
        #endregion

        #region Guard_Guardian_PatrollingLogic
        private void Guard_Guardian_PatrollingLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
        {
            if ( guarded == null || Entity.GuardingOffsets.Count <= 0 )
            {
                guarded = null;
                Entity.GuardingOffsets.Clear();
                Entity.Guarding.PrimaryKeyID = 0;
                Entity.Guarding.Ref = null;

                Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
                int bestFactionIndex = Entity.PlanetFaction.GetIndexOfMostAnnoyingFaction( Context, true );
                Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, bestFactionIndex );
            }
            else
            {
                bool needNewOrder = false;
                if ( Entity.NextGuardingOffsetIndex >= Entity.GuardingOffsets.Count )
                    Entity.NextGuardingOffsetIndex = 0;
                ArcenPoint offset = Entity.GuardingOffsets[Entity.NextGuardingOffsetIndex];
                ArcenPoint targetPoint = guarded.WorldLocation + offset;
                if ( order == null )
                {
                    if ( !Entity.GetIsWithinRangeOf_VeryBasicCheckOnly( targetPoint, ExternalConstants.Instance.EXTRA_SPACE_FOR_MOVEMENT_ORDERS_BETWEEN_SHIPS ) )
                        needNewOrder = true;
                }
                else if ( order.RelatedSquad.PrimaryKeyID != guarded.PrimaryKeyID &&
                          !order.RelatedPoint.GetHasAnyChanceOfBeingInRange( targetPoint, ExternalConstants.Instance.EXTRA_SPACE_FOR_MOVEMENT_ORDERS_BETWEEN_SHIPS ) )
                    needNewOrder = true;
                if ( needNewOrder )
                {
                    Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
                    EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other );
                    Entity.Orders.QueueOrder( Entity, newOrder );
                }
                else if ( Entity.GuardingOffsets.Count > 1 && order == null )
                    Entity.NextGuardingOffsetIndex++;
            }
        }
        #endregion

        #region Guard_Guardian_AnchoredLogic
        private void Guard_Guardian_AnchoredLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
        {
            if ( guarded == null )
            {
                guarded = null;
                Entity.GuardingOffsets.Clear();
                Entity.Guarding.PrimaryKeyID = 0;
                Entity.Guarding.Ref = null;

                Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
                int bestFactionIndex = Entity.PlanetFaction.GetIndexOfMostAnnoyingFaction( Context, true );
                Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, bestFactionIndex );
            }
        }
        #endregion

        #region DelegateHelper_CheckForGuardFreeingProvocation
        private static ArcenPoint DelegateHelper_CheckForGuardFreeingProvocation_guardPoint;
        private static GameEntity_Squad DelegateHelper_CheckForGuardFreeingProvocation_foundHit;
        private static int DelegateHelper_GuardAggroDistance;
        private static DelReturn DelegateHelper_CheckForGuardFreeingProvocation( GameEntity_Squad otherEntity )
        {
            //if ( otherEntity.PlanetFaction.Faction.Type != FactionType.Player )
            //    return DelReturn.Continue;
            if ( !otherEntity.GetIsWithinRangeOf_VeryBasicCheckOnly( DelegateHelper_CheckForGuardFreeingProvocation_guardPoint, DelegateHelper_GuardAggroDistance ) )
                return DelReturn.Continue;
            if ( otherEntity.GetCurrentCloakingPoints() > 0 )
                return DelReturn.Continue;
            DelegateHelper_CheckForGuardFreeingProvocation_foundHit = otherEntity;
            return DelReturn.Break;
        }
        #endregion

        #region DoSystemStep
        public override void DoSystemStep( FInt EffectiveDeltaTime, ArcenSimContext Context, EntitySystem System )
        {
            if ( System == null )
                return;
            if ( !System.DataForMark.IsFunctionalAtThisMarkLevel )
                return;

            #region tracing
            bool trace = Engine_AIW2.TraceAtAll && System.ParentEntity == GameEntity_Base.CurrentlyHoveredOver && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic );
            ArcenCharacterBuffer tracingBuffer = null;
            if ( trace ) tracingBuffer = new ArcenCharacterBuffer();
            if ( trace ) tracingBuffer.Add( "Tracing DoSystemStep for " ).Add( System.TypeData.InternalName ).Add( " from " ).Add( System.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( System.ParentEntity.PrimaryKeyID );
            #endregion
            if ( EffectiveDeltaTime <= FInt.Zero )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because EffectiveDeltaTime <= FInt.Zero" );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }
            if ( System.TypeData.OnlyFiresOnDeath )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.OnlyFiresOnDeath" );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }

            ArcenRejectionReason disabledReason = System.ForShortTermPlanning_DisabledReason;

            if ( disabledReason != ArcenRejectionReason.Unknown )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because disabledReason == " ).Add( disabledReason.ToString() );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }

            System.TimeUntilNextShot -= EffectiveDeltaTime;
            if ( System.TimeUntilNextShot > FInt.Zero )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TimeUntilNextShot > FInt.Zero" );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }
            System.TimeUntilNextShot = FInt.Zero;

            if ( System.TypeData.FiringTiming == FiringTiming.Never )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.FiringTiming == FiringTiming.Never" );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }
            if ( System.TypeData.Category == EntitySystemCategory.Passive )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.Category == EntitySystemCategory.Passive" );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }

            System.IsAuthorizedToFire = false;
            if ( System.TypeData.Category == EntitySystemCategory.Weapon && System.TypeData.FiringTiming == FiringTiming.WhenParentEntityHit )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.Category == EntitySystemCategory.Weapon and this.TypeData.FiringTiming == FiringTiming.WhenParentEntityHit" );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }

            ArcenRejectionReason preventionReason = System.ForShortTermPlanning_CannotBeFiredReason;
            if ( preventionReason != ArcenRejectionReason.Unknown )
            {
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because preventionReason == " ).Add( preventionReason.ToString() );
                if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                #endregion
                return;
            }

            if ( System.TypeData.Category == EntitySystemCategory.Weapon )
            {
                bool didTrigger = ActuallyFireSalvoAtTargetPriorityList( Context, System, trace, tracingBuffer );
                if ( !didTrigger )
                {
                    #region tracing
                    if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping remainder of logic because ActuallyFireSalvo returned false" );
                    if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                    #endregion
                    return;
                }
            }

            CheckForNonSalvoActivationEffects( Context, System, trace, tracingBuffer );
            #region tracing
            if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
            #endregion
        }
        #endregion

        #region DoSystemOnDeathEffect
        public override void DoSystemOnDeathEffect( ArcenSimContext Context, EntitySystem System )
        {
            if ( System == null )
                return;
            if ( System.TypeData.Category == EntitySystemCategory.Weapon )
                ActuallyFireSalvoFromOnDeath( Context, System, false, null );
            CheckForNonSalvoActivationEffects( Context, System, false, null );
        }
        #endregion

        #region CheckForNonSalvoActivationEffects
        public void CheckForNonSalvoActivationEffects( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer )
        {
            #region tracing
            if ( trace ) tracingBuffer.Add( "\n" ).Add( "Tracing CheckForNonSalvoActivationEffects for " ).Add( System.TypeData.InternalName ).Add( " from " ).Add( System.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( System.ParentEntity.PrimaryKeyID );
            // no current need to trace more than the call itself, add later if needed
            #endregion
            System.TimeUntilNextShot += (FInt)System.GetWeaponReloadTime( true );

            if ( System.TypeData.SpawnsEntity != null )
                System.ParentEntity.SpawnEntity( System.TypeData.SpawnsEntity, System.ParentEntity.CurrentMarkLevel, 
                    System.ParentEntity.FleetMembership.Fleet, 
                    0, //this is a secondary entity, so it doesn't ever get unique slots
                    System.ParentEntity.Orders.BehaviorRelatedFactionIndex, Context );

            if ( System.TypeData.WhiteoutSeconds > 0 )
            {
                System.ParentEntity.Planet.WhiteoutTotalDuration = System.TypeData.WhiteoutSeconds;
                System.ParentEntity.Planet.WhiteoutRemainingDuration = System.TypeData.WhiteoutSeconds;
            }
        }
        #endregion

        #region ActuallyFireSalvoAtTargetPriorityList
        public bool ActuallyFireSalvoAtTargetPriorityList( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer )
        {
            System.LastShotFireAbortCode = 0;
            //System.ParentEntity.DebugText = "TRY SALVO";
            if ( System.TypeData.OnlyFiresOnDeath )
            {
                System.LastShotFireAbortCode = 1;
                return false; //no shot was fired
            }

            int cloakingPointsIfMobileNPC = System.ParentEntity.TypeData.IsMobile && System.ParentEntity.PlanetFaction.Faction.Type != FactionType.Player ?
                System.ParentEntity.GetCurrentCloakingPoints() : 0;

            EntityOrder order = System.ParentEntity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision();
            GameEntity_Squad frdAttackTarget = System.CurrentFRDTarget.GetSquad();
            GameEntity_Squad mainAttackTarget = null;
            if ( order != null && order.TypeData.Type == EntityOrderType.Attack )
                mainAttackTarget = order.RelatedSquad.GetSquad();
            else //no attack order at the moment
            {
                if ( cloakingPointsIfMobileNPC > 0 )
                {
                    System.LastShotFireAbortCode = 2;
                    return false; //don't fire at all if we don't have a direct target and we're cloaked.
                }
            }

            if ( mainAttackTarget != null )
            {
                if ( mainAttackTarget.GetHasBeenDestroyed() || mainAttackTarget.HasBeenRemovedFromSim || mainAttackTarget.SecondsSpentAsRemains > 0 || mainAttackTarget.ToBeRemovedAtEndOfThisFrame ||
                    mainAttackTarget.HasNotYetBeenFullyClaimed || mainAttackTarget.PlanetFaction == null || mainAttackTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject )
                {
                    mainAttackTarget = null;
                    if ( System.ParentEntity.Orders.QueuedOrders.Contains( order ) )
                        System.ParentEntity.Orders.QueuedOrders.Remove( order );
                }
            }
            if ( frdAttackTarget != null )
            {
                if ( frdAttackTarget.GetHasBeenDestroyed() || frdAttackTarget.HasBeenRemovedFromSim || frdAttackTarget.SecondsSpentAsRemains > 0 || frdAttackTarget.ToBeRemovedAtEndOfThisFrame ||
                    frdAttackTarget.HasNotYetBeenFullyClaimed || frdAttackTarget.PlanetFaction == null || frdAttackTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject )
                {
                    frdAttackTarget = null;
                    System.CurrentFRDTarget.SetInternalRef( null );
                }
            }

            //System.ParentEntity.DebugText += " SHOOT?";

            if ( mainAttackTarget == null && frdAttackTarget == null && ( System.targetPriorityList == null || System.targetPriorityList.Count == 0 ) )
            {
                System.LastShotFireAbortCode = 3;
                return false; //no shot was fired
            }

            bool chooseNewFRDIfPossible = ( mainAttackTarget == null && frdAttackTarget == null && System.ParentEntity.Orders.Behavior == EntityBehaviorType.Attacker_Full );
            bool chooseNewAttackMoveIfPossible = ( mainAttackTarget == null && frdAttackTarget == null && System.ParentEntity.Orders.Behavior == EntityBehaviorType.Attacker_PursueOnlyInRange );

            #region tracing
            if ( trace ) tracingBuffer.Add( "\n" ).Add( "Tracing ActuallyFireSalvo for " ).Add( System.TypeData.InternalName ).Add( " from " ).Add( System.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( System.ParentEntity.PrimaryKeyID );
            #endregion

            int totalShotsToFire = System.TypeData.ShotsPerSalvo + (System.ParentEntity.ExtraStackedSquadsInThis / ExternalConstants.Instance.Balance_StacksPerBonusShot);
            
            ArcenPoint originPoint = System.GetWorldLocation();

            int spacingMultiplier = 40;

            int withinSalvoLowEnd = 5 * spacingMultiplier;
            int withinSalvoHighEnd = 10 * spacingMultiplier;
            Planet myPlanet = System.ParentEntity.Planet;

            //System.ParentEntity.DebugText += " SHOOT!";

            bool hasPlayedSoundYet = false;
            //int runningTargetIndex = 0;
            int numberOfShotsFired = 0;
            bool hadAnyOutOfRange = false;
            #region tracing
            if ( trace )
            {
                //tracingBuffer.Add( "\n" ).Add( "\t").Add( "damageMultiplierFromSubsquads" ).Add( ":" ).Add( damageMultiplierFromSubsquads );
                tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "totalShotsToFire" ).Add( ":" ).Add( totalShotsToFire );
                tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "System.targetPriorityList.Count" ).Add( ":" ).Add( System.targetPriorityList.Count );
            }
            #endregion
            {
                FInt runningDelay = FInt.Zero;
                int targetIndex = -2;
                int attemptCount = 100;
                while ( numberOfShotsFired < totalShotsToFire && targetIndex < System.targetPriorityList.Count && attemptCount-- > 0 )
                {
                    //System.ParentEntity.DebugText += " " + targetIndex;
                    #region tracing
                    if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetIndex " ).Add( targetIndex );
                    #endregion
                    GameEntity_Squad potentialTarget;
                    {
                        if ( System.targetPriorityList.Count <= targetIndex )
                            break;
                        if ( targetIndex == -2 ) //try to attack the main focus of this entity if it has orders
                        {
                            if ( mainAttackTarget == null )
                            {
                                targetIndex++;
                                continue;
                            }
                            potentialTarget = mainAttackTarget;
                        }
                        else if ( targetIndex == -1 ) //try to attack the FRD focus of this system
                        {
                            if ( frdAttackTarget == null || frdAttackTarget == mainAttackTarget )
                            {
                                targetIndex++; //can't attack the same thing more than once
                                continue;
                            }
                            potentialTarget = frdAttackTarget;
                        }
                        else
                        {
                            if ( cloakingPointsIfMobileNPC > 0 && numberOfShotsFired == 0 )
                                return false; //don't fire at all if we don't have a direct target that we can hit and we're cloaked

                            potentialTarget = System.targetPriorityList[targetIndex].GetSquad();
                            if ( potentialTarget == null || potentialTarget == frdAttackTarget || potentialTarget == mainAttackTarget )
                            {
                                targetIndex++; //can't attack the same thing more than once
                                continue;
                            }
                        }
                        //System.ParentEntity.DebugText += " attempt";
                        #region tracing
                        if ( trace )
                        {
                            tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetIndex" ).Add( ":" ).Add( targetIndex );
                            //tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetSquadID" ).Add( ":" ).Add( targetPriority.TargetSquadID );
                            tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "autoTarget" ).Add( ":" ).Add( potentialTarget?.TypeData.InternalName ?? "null" );
                        }
                        #endregion
                        if ( potentialTarget == null || potentialTarget.HasBeenRemovedFromSim || potentialTarget.ToBeRemovedAtEndOfThisFrame ||
                            potentialTarget.SecondsSpentAsRemains > 0 || potentialTarget.GetHasBeenDestroyed() ||
                            potentialTarget.HasNotYetBeenFullyClaimed || potentialTarget.PlanetFaction == null || potentialTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject )
                        {
                            //if ( potentialTarget != null )//&& potentialTarget.TypeData.InternalName.Contains( "Spider" ) )
                            //    System.ParentEntity.DebugText += potentialTarget.HasBeenRemovedFromSim + " " + potentialTarget.ToBeRemovedAtEndOfThisFrame + " " + potentialTarget.SecondsSpentAsRemains + " " + potentialTarget.TypeData.InternalName;
                            //else
                            //    System.ParentEntity.DebugText += " null target!";
                            #region tracing
                            if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "target null, done here" );
                            #endregion
                            //this thing is dead or dying, don't shoot it and instead shoot something else if we can
                            if ( targetIndex >= 0 )
                                System.targetPriorityList.RemoveAt( targetIndex ); //don't increment targetIndex if was an actual target
                            else
                                targetIndex++; //do increase targetIndex if it was an auto-target from less than zero
                            continue;
                        }
                        if ( potentialTarget.Planet != myPlanet )
                        {
                            //System.ParentEntity.DebugText += potentialTarget.Planet.Name + " " + myPlanet.Name + " " + potentialTarget.TypeData.InternalName;
                            //If you suspect foul play with a background thread putting junk in here, uncomment this.  Results on 8/29/2018 were good.
                            //ArcenDebugging.ArcenDebugLog( "ERROR: Firing between planets! " + System.TypeData.InternalName + " tried to shoot a " + autoTarget.TypeData.InternalName +
                            //    " from planet " + ( myPlanet == null ? "NULL" : myPlanet.Name ) + " to planet " + ( autoTarget.Planet == null ? "NULL" : autoTarget.Planet.Name ), Verbosity.ShowAsError );

                            //this thing left the current planet, don't shoot it and instead shoot something else if we can
                            if ( targetIndex >= 0 )
                                System.targetPriorityList.RemoveAt( targetIndex ); //don't increment targetIndex if was an actual target
                            else
                                targetIndex++; //do increase targetIndex if it was an auto-target from less than zero
                            continue;
                        }
                        //if it would be overkilled and it's not under a shield, ignore it for now.
                        if ( potentialTarget.ProtectingShields.Count == 0 && potentialTarget.GetExpectsToBeOverkilled( 0 ) )
                        {
                            targetIndex++; //we will skip it, but we will not take it out of our list just yet since the overkill may not really happen
                            continue;
                        }
                        targetIndex++;
                    }

                    //the first one is probably the best
                    if ( chooseNewFRDIfPossible )
                    {
                        System.CurrentFRDTarget.SetInternalRef( potentialTarget );
                        chooseNewFRDIfPossible = false;
                    }

                    if ( !System.GetIsTargetInRange( potentialTarget, RangeCheckType.ForActualFiring ) )
                    {
                        hadAnyOutOfRange = true;
                        //System.ParentEntity.DebugText += " not in range";
                        continue; //not all of them will be in range!  This is ok and expected.  Possibly none of them will be in range!
                    }

                    //the first one is probably the best -- difference is MUST be in range
                    if ( chooseNewAttackMoveIfPossible )
                    {
                        System.CurrentFRDTarget.SetInternalRef( potentialTarget );
                        chooseNewAttackMoveIfPossible = false;
                    }

                    numberOfShotsFired++;

                    //System.ParentEntity.DebugText += " FIRE!";
                    InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, potentialTarget, originPoint,
                        Context, System, trace, tracingBuffer, withinSalvoLowEnd, withinSalvoHighEnd );
                }
            }

            if ( numberOfShotsFired > 0 )
            {
                System.LastGameSecondIFiredShot = World_AIW2.Instance.GameSecond;

                // OLD LOGIC: if could not fire full salvo, do partial refund on reload timer
                // Chris's new logic, for now at least: you just are out of luck, better luck next time. ;)
                //if ( numberOfShotsFired < totalShotsToFire && totalShotsToFire > 0 )
                //{
                //    FInt percentUnused = ( (FInt)totalShotsToFire - (FInt)numberOfShotsFired ) / (FInt)totalShotsToFire;
                //    System.TimeUntilNextShot -= percentUnused * (FInt)System.GetWeaponReloadTime(true);
                //}

                if ( System.ParentEntity.GetMaxCloakingPoints() > 0 )
                {
                    if ( System.ParentEntity.GetCurrentCloakingPoints() > 0 )
                    {
                        int pointsLost = ( System.ParentEntity.GetMaxCloakingPoints() * ExternalConstants.Instance.Balance_CurrentCloakingPercentLossFromFiring ).GetNearestIntPreferringHigher();
                        if ( pointsLost < 1 )
                            pointsLost = 1;
                        System.ParentEntity.CloakingPointsLost += pointsLost;
                    }
                    System.ParentEntity.GameSecondOfLastCloakingPointLoss = World_AIW2.Instance.GameSecond;
                }
                return true;
            }
            else //no shot was fired!
            {
                if ( hadAnyOutOfRange )
                    System.LastShotFireAbortCode = 4;
                else
                    System.LastShotFireAbortCode = 5;
                //in cases where we didn't fire a shot, wait a random amount of time between 0 and 0.2 seconds before trying again
                //this adds more flavor into how ships shoot, for one, but it also prevents hammering this method every frame
                System.TimeUntilNextShot = FInt.FromParts( 0, Context.RandomToUse.Next( 0, 100 ) ) +
                    FInt.FromParts( 0, Context.RandomToUse.Next( 0, 100 ) );
                return false;
            }
        }

        #region ActuallyFireSalvoFromOnDeath
        public void ActuallyFireSalvoFromOnDeath( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer )
        {
            bool hasPlayedSoundYet = false;
            FInt runningDelay = FInt.Zero;
            ArcenPoint originPoint = System.GetWorldLocation();
            InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, System.ParentEntity, originPoint,
                Context, System, trace, tracingBuffer, 1, 3 );
        }
        #endregion

        #region InternalCreateActualShotForSalvo
        private void InternalCreateActualShotForSalvo( ref bool hasPlayedSoundYet, ref FInt runningDelay, GameEntity_Squad target, ArcenPoint originPoint,
            ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer, int withinSalvoLowEnd, int withinSalvoHighEnd )
        {
            #region Check To See If Relevant: On Background Planets, Shots Should Insta-Hit And Not Spawn Entities
            if ( System.ParentEntity != null && System.ParentEntity.Planet != null && System.ParentEntity.Planet.BattleStatus_ShotsInstaHitUnlessPlayer )
            {
                if ( System.ParentEntity.PlanetFaction != null && System.ParentEntity.PlanetFaction.Faction != null &&
                    System.ParentEntity.PlanetFaction.Faction.Type != FactionType.Player )
                {
                    if ( target == null || ( target.PlanetFaction != null && target.PlanetFaction.Faction != null &&
                        target.PlanetFaction.Faction.Type != FactionType.Player ) )
                    {
                        //we're firing at something OTHER than a player ship, and we're not a player ship, so  do the thing
                        #region It IS Relevant, So Do The Insta-Hit
                        if ( !this.CheckForShotAOEDetonation( null, target, System, Context ) )
                            this.DoShotHitLogic( null, System, target, Context );
                        #endregion
                        return;
                    }
                }
            }
            #endregion

            GameEntity_Shot newShot = SpawnShot( System, originPoint, Context );
            #region tracing
            if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "spawned actual shot:" ).Add( newShot.PrimaryKeyID ).Add( ":" ).Add( newShot.TypeData.InternalName );
            #endregion
            newShot.SetTarget( target );

            if ( System.TypeData.FiresSalvoSequentially )
            {
                runningDelay += FInt.FromParts( 0, Context.RandomToUse.Next( withinSalvoLowEnd, withinSalvoHighEnd ) );
                newShot.RemainingDelayUntilEntersSim = runningDelay;
            }
            else
                newShot.RemainingDelayUntilEntersSim = FInt.Zero;
            if ( !hasPlayedSoundYet )
            {
                hasPlayedSoundYet = true; //otherwise we spam the queue and I'm just going to discard them anyway!
                newShot.PlayJustFiredSound();
            }
        }
        #endregion
        
        public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, ArcenSimContext Context )
        {
            FInt dummy;
            return DoShotHitLogic( ShotOrNull, OriginSystemForShot, Target, false, (FInt)100, out dummy, Context );
        }

        public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, ArcenSimContext Context )
        {
            FInt dummy;
            return DoShotHitLogic( ShotOrNull, OriginSystemForShot, Target, HonorFiniteHitCountAOE, (FInt)100, out dummy, Context );
        }

        public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, FInt PercentOfTotalAttackPowerForThisHit, out FInt PercentOfTotalAttackPowerUsedForThisHit, ArcenSimContext Context )
        {
            return DoShotHitLogic( ShotOrNull, OriginSystemForShot, Target, false, PercentOfTotalAttackPowerForThisHit, out PercentOfTotalAttackPowerUsedForThisHit, Context );
        }

        public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, FInt PercentOfTotalAttackPowerForThisHit, out FInt PercentOfTotalAttackPowerUsedForThisHit, ArcenSimContext Context )
        {
            PercentOfTotalAttackPowerUsedForThisHit = FInt.Zero;
            if ( !OriginSystemForShot.GetCanHitByDesireOrNot_AndIAlreadyKnowIAmAWeapon( Target ) )
                return false;

            bool wasAlive = !Target.GetHasBeenDestroyed();

            GameEntity_Squad protectingShieldThatTookTheHit = null;

            if ( OriginSystemForShot != null )
            {
                OriginSystemForShot.LastGameSecondMyShotHit = World_AIW2.Instance.GameSecond;
                OriginSystemForShot.LastTotalDamageMyShotDidCaused = 0;
                OriginSystemForShot.LastDamageAbortCode = 0;
            }

            {
                #region tracing
                bool trace = Engine_AIW2.TraceAtAll;
                if ( trace )
                {
                    if ( Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && OriginSystemForShot.ParentEntity == GameEntity_Base.CurrentlyHoveredOver )
                    { } //trace this
                    else if ( Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.ShotHitLogic ) )
                    { } //trace this
                    else
                        trace = false;
                }
                ArcenCharacterBuffer tracingBuffer = null;
                if ( trace ) tracingBuffer = new ArcenCharacterBuffer();
                if ( trace ) tracingBuffer.Add( "Tracing DoHitLogic for shot from " ).Add( OriginSystemForShot.TypeData.InternalName ).Add( " from " ).Add( OriginSystemForShot.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( OriginSystemForShot.ParentEntity.PrimaryKeyID );
                #endregion
                int attackPowerAgainstThisTarget = OriginSystemForShot.GetAttackPowerAgainst( Target, tracingBuffer, true );
                int adjustedAttackPower = ((attackPowerAgainstThisTarget * PercentOfTotalAttackPowerForThisHit) / 100).IntValue;
                #region tracing
                if ( trace ) tracingBuffer.Add( "\n" ).Add( "int adjustedAttackPower = ( ( attackPowerAgainstThisTarget " +  attackPowerAgainstThisTarget + " * PercentOfTotalAttackPowerForThisHit " + PercentOfTotalAttackPowerForThisHit +" ) / 100 ).IntValue = " ).Add( adjustedAttackPower );
                #endregion
                int actualDamageDone, damageAbortCode;
                protectingShieldThatTookTheHit = Target.TakeDamage( adjustedAttackPower, OriginSystemForShot, ShotOrNull, false, false, HonorFiniteHitCountAOE, OriginSystemForShot.TypeData.MaxStacksToKill, out actualDamageDone, out damageAbortCode, Context );
                if ( OriginSystemForShot != null )
                {
                    OriginSystemForShot.LastTotalDamageMyShotDidCaused += actualDamageDone;
                    if ( damageAbortCode != 0 )
                        OriginSystemForShot.LastDamageAbortCode = damageAbortCode;
                }

                #region tracing
                if ( trace )
                {
                    tracingBuffer.Add( "\n" ).Add( "actualDamageDone = " ).Add( actualDamageDone );
                    ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                }
                #endregion
                if ( actualDamageDone <= 0 )
                    return false;
                if ( attackPowerAgainstThisTarget > 0 ) // it will be, but just to be sure
                {
                    if ( actualDamageDone == adjustedAttackPower )
                        PercentOfTotalAttackPowerUsedForThisHit = PercentOfTotalAttackPowerForThisHit; // otherwise sometimes precision errors can make it look like the whole shot was not used
                    else
                        PercentOfTotalAttackPowerUsedForThisHit += ((FInt)(actualDamageDone * 100) / (FInt)attackPowerAgainstThisTarget);
                }
                if ( OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt != FInt.Zero && actualDamageDone > 0 )
                {
                    if ( OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt < FInt.Zero )
                    {
                        int selfDamage = (actualDamageDone * -OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt).IntValue;
                        OriginSystemForShot.ParentEntity.TakeDamage( selfDamage, null, null, true, Context );
                    }
                    else
                    {
                        int selfHealing = (actualDamageDone * OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt).IntValue;
                        selfHealing -= OriginSystemForShot.ParentEntity.TakeHullRepair( selfHealing );
                        if ( selfHealing > 0 )
                            OriginSystemForShot.ParentEntity.TakeShieldRepair( selfHealing );
                    }
                }
            }

            World_AIW2.Instance.TotalShotsHit++;

            bool wholeSquadKilled = wasAlive && Target.GetHasBeenDestroyed();
            int shipsKilled = wholeSquadKilled ? 1 : 0;

            if ( ShotOrNull != null && ShotOrNull.Planet != null && ShotOrNull.Planet.BattleStatus == PlanetBattleStatus.Tier1_PlayerLookingAtMe )
            {
                if ( ShotOrNull.VisualLinkObject_GenericOnly != null || ShotOrNull.InstancedRenderer != null )
                {
                    if ( ShotOrNull.InstancedRenderer != null )
                        ShotOrNull.InstancedRenderer.ReactToShotHittingSquad( Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled );
                    else
                        Engine_AIW2.Instance.PresentationLayer.ReactToShotHittingSquad( ShotOrNull, Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled );
                }
                else
                {
                    if ( Target.InstancedRenderer != null )
                        Target.InstancedRenderer.ReactToShotHittingSquad( Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled );
                }
            }
            else
            {
                if ( (shipsKilled > 0 || wholeSquadKilled) && Target.TypeData.Category == GameEntityCategory.Ship )
                {
                    //ArcenDebugging.ArcenDebugLog( "Killed ship! " + Target.TypeData.InternalName + " InstancedRenderer = " + (Target.InstancedRenderer == null ? "null" : "ok"), Verbosity.DoNotShow );
                    Target.PlayJustDiedSoundIfNotOnCurrentLocalPlanet( Context, wholeSquadKilled );
                }
            }

            return true;
        }

        public override bool CheckForShotAOEDetonation( GameEntity_Shot ShotOrNull, GameEntity_Squad TargetOrNull, EntitySystem OriginSystemForShot, ArcenSimContext Context )
        {
            int aoe = OriginSystemForShot.DataForMark.ShotAreaOfEffect;
            if ( aoe > 0 )
            {
                PlanetFaction myFaction = OriginSystemForShot.ParentEntity.PlanetFaction;
                if ( myFaction == null )
                    return true; //didn't work, but still an AOE shot so report true

                PlanetFaction fac;
                ArcenPoint myLoc = (ShotOrNull != null ? ShotOrNull.WorldLocation : OriginSystemForShot.ParentEntity.WorldLocation );
                Planet myPlanet = (ShotOrNull != null ? ShotOrNull.Planet : OriginSystemForShot.ParentEntity.Planet);
                Context.WorkingAOETargetsToHitList.Clear();
                int distance;
                for ( int i = 0; i < myPlanet.Factions.Count; i++ )
                {
                    fac = myPlanet.Factions[i];
                    if ( fac == null )
                        continue;
                    //doing this only once per faction, and more centrally, is quite efficient when there's a lot of entities
                    if ( !OriginSystemForShot.TypeData.AOEHitsFriendlyTargets &&
                         !OriginSystemForShot.ParentEntity.PlanetFaction.GetIsHostileTowards( fac ) )
                        continue;
                    fac.Entities.DoForEntities( delegate ( GameEntity_Squad otherEntity )
                    {
                        distance = aoe + otherEntity.DataForMark.Radius;

                        //first very raw check, good for weeding out
                        if ( !otherEntity.WorldLocation.GetHasAnyChanceOfBeingInRange( myLoc, distance ) )
                            return DelReturn.Continue;
                        if ( otherEntity.GetDamageForbiddenToThisTargetByTargetingRules( myFaction, TargetOrNull != null && otherEntity.PrimaryKeyID == TargetOrNull.PrimaryKeyID ) )
                            return DelReturn.Continue;
                        //all righty, it's close so do the more expensive check (still not that expensive)
                        if ( otherEntity.WorldLocation.GetDistanceTo( myLoc, false ) > distance )
                            return DelReturn.Continue;

                        Context.WorkingAOETargetsToHitList.Add( otherEntity );
                        return DelReturn.Continue;
                    } );
                }
                int numberOfTargetsToHit = Context.WorkingAOETargetsToHitList.Count;
                int maxTargets = OriginSystemForShot.TypeData.MaximumNumberOfTargetsHitPerShot;
                if ( maxTargets > 0 )
                    numberOfTargetsToHit = maxTargets;
                if ( numberOfTargetsToHit <= Context.WorkingAOETargetsToHitList.Count )
                    Engine_Universal.Randomize( Context.WorkingAOETargetsToHitList, Context.RandomToUse, 3 );
                if ( OriginSystemForShot.TypeData.AOESpreadsDamageAmongAvailableTargets )
                {
                    if ( Context.WorkingAOETargetsToHitList.Count > 0 )
                    {
                        int totalOutput = OriginSystemForShot.DataForMark.CalculateActualDamagePerShot( OriginSystemForShot.ParentEntity );
                        int remainingOutput = totalOutput;
                        int maxLoopCount = 100;
                        bool checkAgain = true;
                        while ( remainingOutput > 0 && checkAgain && maxLoopCount-- > 0 )
                        {
                            checkAgain = false;
                            
                            FInt portionPerEachOutOf100 = Mat.Max( (FInt)10, (FInt)100 / Context.WorkingAOETargetsToHitList.Count ); // always tries to apply at least 10%, to avoid this getting into fiddling small change and burning inordinate cpu
                                                                                                                                     // Badger doesn't really like this logic for  AOE shots that do a huge amount of damage, but he concedes it is more efficient
                                                                                                                                     // and if there are a ton of targets then they are probably stacked, so heavy damage to a single target is probably fine
                            for ( int i = 0; i < Context.WorkingAOETargetsToHitList.Count; i++ )
                            {
                                FInt portionUsedOutOf100;
                                if ( remainingOutput <= 0 ) //if we've already done the total damage we were allowed to do, we're done
                                {
                                    checkAgain = false;
                                    continue;
                                }
//                                ArcenDebugging.ArcenDebugLogSingleLine(OriginSystemForShot.TypeData.GetDisplayName() + " is trying to do " + portionPerEachOutOf100 + "% damage to " + Context.WorkingAOETargetsToHitList[i].ToString() +". remaining damage before the hit: " + remainingOutput , Verbosity.DoNotShow );
                                EntitySimLogic.Instance.DoShotHitLogic( ShotOrNull, OriginSystemForShot, Context.WorkingAOETargetsToHitList[i], portionPerEachOutOf100, out portionUsedOutOf100, Context );
//                                ArcenDebugging.ArcenDebugLogSingleLine("\tPortion used: " + portionUsedOutOf100, Verbosity.DoNotShow );
                                if ( portionUsedOutOf100 <= 0 )
                                {
                                    Context.WorkingAOETargetsToHitList.RemoveAt( i-- );
                                    continue;
                                }
                                int damageDone = Math.Max( 1, ((portionUsedOutOf100 * totalOutput) / 100).GetNearestIntPreferringHigher() );
                                if ( damageDone <= 0 )
                                {
                                    Context.WorkingAOETargetsToHitList.RemoveAt( i-- );
                                    continue;
                                }
                                remainingOutput -= damageDone;
                                checkAgain = true;
                            }
                        }
                    }
                }
                else
                {
                    Context.WorkingAOETargetsThatHaveBeenHitList.Clear();
                    for ( int i = 0; i < Context.WorkingAOETargetsToHitList.Count; i++ )
                    {
                        if ( numberOfTargetsToHit <= 0 )
                            break;
                        if ( EntitySimLogic.Instance.DoShotHitLogic( ShotOrNull, OriginSystemForShot, Context.WorkingAOETargetsToHitList[i], true, Context ) )
                            numberOfTargetsToHit--;
                    }
                    Context.WorkingAOETargetsThatHaveBeenHitList.Clear();
                }

                if ( ShotOrNull != null )
                {
                    ShotOrNull.PostExecution_EmitOnAOECount++;
                    ShotOrNull.PostExecution_AOESimLocation = ShotOrNull.WorldLocation;
                }
                return true;
            }
            else if ( OriginSystemForShot != null && OriginSystemForShot.TypeData.BeamLengthMultiplier > FInt.Zero )
            {
                if  ( TargetOrNull == null )
                    return true; //didn't work, but still an AOE shot so report true
                GameEntity_Squad targetEntity = TargetOrNull;

                #region tracing
                bool trace = Engine_AIW2.TraceAtAll && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && OriginSystemForShot.ParentEntity == GameEntity_Base.CurrentlyHoveredOver;
                ArcenCharacterBuffer tracingBuffer = null;
                if ( trace ) tracingBuffer = new ArcenCharacterBuffer();
                if ( trace ) tracingBuffer.Add( "Tracing CheckForAOEDetonation for shot from " ).Add( OriginSystemForShot.TypeData.InternalName ).Add( " from " ).Add( OriginSystemForShot.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( OriginSystemForShot.ParentEntity.PrimaryKeyID );
                #endregion

                if ( OriginSystemForShot.TypeData.HitsAllIntersectingTargets )
                {
                    ArcenPoint originPoint = OriginSystemForShot.ParentEntity.WorldLocation;
                    ArcenPoint targetPoint = targetEntity.WorldLocation;
                    AngleDegrees angle = originPoint.GetAngleToDegrees( targetPoint );
                    int beamLength = (OriginSystemForShot.DataForMark.CalculateActualRange( OriginSystemForShot.ParentEntity ) * OriginSystemForShot.TypeData.BeamLengthMultiplier).IntValue;
                    int beamCount = OriginSystemForShot.TypeData.NumberBeamsToFire;
                    AngleDegrees beamBeginAngle = beamCount == 1 ? angle : angle.Add( -((OriginSystemForShot.TypeData.DegreesOffsetPerBeam * beamCount) / 2) );
                    #region tracing
                    if ( trace )
                    {
                        tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "originPoint" ).Add( ":" ).Add( originPoint.ToString() );
                        tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetPoint" ).Add( ":" ).Add( targetPoint.ToString() );
                        tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "angle" ).Add( ":" ).Add( angle.ToString() );
                        tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "beamLength" ).Add( ":" ).Add( beamLength );
                        tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "beamBeginAngle" ).Add( ":" ).Add( beamBeginAngle.ToString() );
                    }
                    #endregion
                    EntityBeamWeaponLineSet resultingBeamWeaponSet = EntityBeamWeaponLineSet.Create();
                    resultingBeamWeaponSet.TypeData = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.BeamCannon];
                    resultingBeamWeaponSet.OriginPoint = originPoint;
                    AngleDegrees workingAngle = beamBeginAngle;
                    int maxTargets = OriginSystemForShot.TypeData.MaximumNumberOfTargetsHitPerShot != 0 ? OriginSystemForShot.TypeData.MaximumNumberOfTargetsHitPerShot : -1;
                    for ( int i = 0; i < beamCount; i++, workingAngle += OriginSystemForShot.TypeData.DegreesOffsetPerBeam )
                    {
                        //GetPointAtAngleAndDistance is not remotely precise enough for our purposes here!
                        ArcenPoint endPoint = originPoint.GetPointAtAngleAndDistance( workingAngle, beamLength );
                        List<GameEntity_Squad> intersectingTargets = OriginSystemForShot.GetEnemyEntitiesIntersectingInstaFireConicalShot( originPoint, endPoint, beamLength, targetEntity, Context, tracingBuffer );

                        for ( int k = 0; k < intersectingTargets.Count; k++ )
                        {
                            // If we have a max amount of targets, stop upon reaching that amount.
                            if ( maxTargets > 1 && k >= maxTargets )
                            {
                                endPoint = intersectingTargets[k - 1].WorldLocation;
                                break;
                            }
                            GameEntity_Squad intersectedTarget = intersectingTargets[k];
                            EntitySimLogic.Instance.DoShotHitLogic( ShotOrNull, OriginSystemForShot, intersectedTarget, Context );
                        }
                        intersectingTargets.Clear();
                        //UnityEngine.Debug.Log( i + " " + beamCount + " " + workingAngle + " " + endPoint + " " + originPoint.GetPointAtAngleAndDistance( workingAngle, beamLength ) );
                        resultingBeamWeaponSet.TargetPoints.Add( endPoint );
                    }
                    OriginSystemForShot.ParentEntity.Visual_NextOutgoingBeamWeaponLineSets.Add( resultingBeamWeaponSet );
                    #region tracing
                    if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
                    #endregion
                    return true;
                }
                else //only hits the target
                {
                    EntityBeamWeaponLineSet resultingBeamWeaponSet = EntityBeamWeaponLineSet.Create();
                    resultingBeamWeaponSet.TypeData = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.Laser];
                    resultingBeamWeaponSet.OriginPoint = OriginSystemForShot.ParentEntity.WorldLocation;
                    resultingBeamWeaponSet.TargetPoints.Add( targetEntity.WorldLocation );
                    OriginSystemForShot.ParentEntity.Visual_NextOutgoingBeamWeaponLineSets.Add( resultingBeamWeaponSet );
                    return true;
                }
            }
            //not an AOE shot
            return false;
        }

        public GameEntity_Shot SpawnShot( EntitySystem System, ArcenPoint StartingLocation, ArcenSimContext Context )
        {
            return SpawnShot( System, System.TypeData.ShotTypeData, StartingLocation, Context );
        }

        public GameEntity_Shot SpawnShot( EntitySystem System, GameEntityTypeData effectiveType, ArcenPoint StartingLocation, ArcenSimContext Context )
        {
            GameEntity_Shot newShot = GameEntity_Shot.CreateNew( System.ParentEntity.PlanetFaction, effectiveType, StartingLocation, Context );
            newShot.Origin = System;
            return newShot;
        }
        #endregion

        #region DoWormholeTraversalLogic
        public override void DoWormholeTraversalLogic( ArcenSimContext Context )
        {
            bool didShipsGoThroughWormhole = false;
            int currentPlanetIndex = -1;
            ArcenPoint shipWormholePoint = ArcenPoint.ZeroZeroPoint;
            if ( PlayerAccount.Local != null )
                currentPlanetIndex = PlayerAccount_AIW2.Local.ViewingPlanetIndex;
            //bool onGalaxyMap = Engine_AIW2.Instance.CurrentGameViewMode != GameViewMode.MainGameView;
            for ( int i = 0; i < World_AIW2.Instance.Galaxies[0].Planets.Count; i++ )
            {
                Planet planet = World_AIW2.Instance.Galaxies[0].Planets[i];
                if ( !planet.BattleStatus_ProcessThisSimStep )
                    continue;
                for ( int j = 0; j < planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame.Count; j++ )
                {
                    GameEntity_Squad entity = planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame[j];
                    EntityOrder order = entity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision();
                    if ( order == null || order.TypeData.Type != EntityOrderType.Wormhole )
                        continue;
                    if (entity.ActiveHack_Target != 0)
                        continue;
                    if ( entity.GetHasBeenDestroyed() || entity.ToBeRemovedAtEndOfThisFrame )
                        continue;
                    Planet myPlanet = entity.Planet;
                    if ( myPlanet != planet )
                        continue;
                    GameEntity_Other thisSideWormhole = order.GetWormholeToOtherPlanet( entity );
                    if ( thisSideWormhole == null )
                        continue;
                    Planet targetPlanet = thisSideWormhole.GetLinkedPlanet();
                    if ( targetPlanet == null || targetPlanet == myPlanet )
                        continue;
                    GameEntity_Other otherSideWormhole = targetPlanet.GetWormholeTo( myPlanet );
                    if ( otherSideWormhole == null )
                        continue;
                    PlanetFaction targetPlanetDestinationFaction = targetPlanet.GetPlanetFactionForFaction( entity.PlanetFaction.Faction );

                    if ( !didShipsGoThroughWormhole )
                    {
                        if ( currentPlanetIndex == targetPlanet.PlanetIndex )
                        {
                            shipWormholePoint = otherSideWormhole.WorldLocation;
                            didShipsGoThroughWormhole = true;
                        }
                        else if ( currentPlanetIndex == myPlanet.PlanetIndex )
                        {
                            shipWormholePoint = thisSideWormhole.WorldLocation;
                            didShipsGoThroughWormhole = true;
                        }
                    }

                    //ArcenPoint exitAnimationStartingPoint = entity.WorldLocation;
                    //AngleDegrees exitAnimationAngle = Engine_AIW2.Instance.CombatCenter.GetAngleToDegrees( thisSideWormhole.WorldLocation );
                    //int exitAnimationDistance = Engine_AIW2.Instance.CombatCenter.GetDistanceTo( thisSideWormhole.WorldLocation, false ) * 1000;
                    //ArcenPoint exitAnimationTargetPoint = Engine_AIW2.Instance.CombatCenter.GetPointAtAngleAndDistance( exitAnimationAngle, exitAnimationDistance );
                    //CHRIS_TODO: animation of ship zipping off into the distance from exitAnimationStartingPoint to exitAnimationTargetPoint (to get those, uncomment the four lines above)
                    //bool isPlayerFactionEntity = entity.PlanetFaction.Faction.Type == FactionType.Player;

                    //bool shouldSelectOnOtherSide = false;
                    //if ( onGalaxyMap && isPlayerFactionEntity &&
                    //     entity.GetIsSelected() )
                    //    shouldSelectOnOtherSide = true;
                    entity.ForceUnselect( true );

                    entity.RemoveFromSpatialPartitioning( false, InstancedRendererDeactivationReason.WentThroughWormhole );
                    
                    //AngleDegrees angleToWormhole = Engine_AIW2.Instance.CombatCenter.GetAngleToDegrees( otherSideWormhole.WorldLocation );
                    //int distanceToWormhole = Engine_AIW2.Instance.CombatCenter.GetDistanceTo( otherSideWormhole.WorldLocation, false );
                    ArcenPoint exitPoint = otherSideWormhole.WorldLocation;
                    entity.GameSecondEnteredThisPlanet = World_AIW2.Instance.GameSecond;
                    entity.SetWorldLocation( exitPoint );

                    entity.PlanetFaction.SwitchToFaction( entity, targetPlanetDestinationFaction );
                    entity.AddToNewPlanet( targetPlanet );

                    //clear any decollision orders that were previously in place before going through the wormhole.
                    entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.OnlyClearDecollisionOrdersAndNothingElse, ClearSource.DoNotClearAnythingExceptDecollision );

                    //if ( shouldSelectOnOtherSide )
                    //    World_AIW2.Instance.EntitiesToSelectNextFrame.Add( entity );
                    
                    if ( entity.CurrentlyStrongestTractorSourceHittingThese.Count > 0 )
                    {
                        for ( int k = 0; k < entity.CurrentlyStrongestTractorSourceHittingThese.Count; k++ )
                        {
                            int id = entity.CurrentlyStrongestTractorSourceHittingThese[k];
                            GameEntity_Squad tractoredEntity = World_AIW2.Instance.GetEntityByID_Squad( id );
                            if ( tractoredEntity == null )
                                continue;
                            if ( tractoredEntity.GetMatches( EntityRollupType.ProjectsForcefield ) && tractoredEntity.ShieldPointsLost < tractoredEntity.DataForMark.ShieldPoints )
                                continue; // can tractor something with a shield, but you can't actually tow it
                            if ( tractoredEntity.Planet == targetPlanet ) // might already be there if both are still mobile and both are trying to transit the same wormhole
                                continue;
                            tractoredEntity.RemoveFromSpatialPartitioning( false, InstancedRendererDeactivationReason.DraggedThroughWormholeByTractorBeam );
                            tractoredEntity.PlanetFaction.SwitchToFaction( tractoredEntity, targetPlanet.GetPlanetFactionForFaction( tractoredEntity.PlanetFaction.Faction ) );

                            tractoredEntity.GameSecondEnteredThisPlanet = World_AIW2.Instance.GameSecond;
                            //for tractored entities, pop them out of the wormhole at a random place near the exitPoint.
                            //It might be more efficient to calculate this range elsewhere and cache it?
                            int tractorRange = 0;
                            for ( int index = 0; index < entity.Systems.Count; index++)
                            {
                                EntitySystem system = entity.Systems[index];
                                if ( system.TypeData.ForMark[entity.DataForMark.MarkLevel.Ordinal].TractorCount <= 0 )
                                    continue;
                                if ( system.ForShortTermPlanning_DisabledReason != ArcenRejectionReason.Unknown )
                                    continue;
                                tractorRange = Math.Max( tractorRange, system.DataForMark.TractorRange );
                            }
                            AngleDegrees angle = AngleDegrees.Create( (float)Context.RandomToUse.Next( 1, 360 ) );
                            ArcenPoint point = exitPoint.GetPointAtAngleAndDistance(angle, tractorRange);
                            tractoredEntity.SetWorldLocation( point );

                            tractoredEntity.AddToNewPlanet( targetPlanet );
                        }
                    }

                    //ArcenPoint entranceAnimationTargetPoint = entity.WorldLocation;
                    //AngleDegrees entranceAnimationAngle = angleToWormhole;
                    //int entranceAnimationDistance = distanceToWormhole * 1000;
                    //ArcenPoint entranceAnimationStartingPoint = Engine_AIW2.Instance.CombatCenter.GetPointAtAngleAndDistance( entranceAnimationAngle, entranceAnimationDistance );
                    //CHRIS_TODO: animation of ship zipping in from the distance from entranceAnimationStartingPoint to entranceAnimationTargetPoint (to get those, uncomment the four lines above)
                }
                planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame.Clear();
            }

            if ( didShipsGoThroughWormhole )
                Engine_AIW2.Instance.PresentationLayer.PlaySoundByType( SFXItemType_Positional.WormholeTransit, shipWormholePoint );
        }
        #endregion
    }
}
EntitySimLogicImplementation.cs (101,236 bytes)   

Chris_McElligottPark

Dec 3, 2019 4:38 pm

administrator   ~0054782

Thanks!

* Implemented some code changes from StarKelp that improve performance of the lance weapons be 12-15% in heavy usage based on his tests, and they now hit all targets in a line. This may have some balance implications based on a code review, so please let us know what folks think about this change. It's a small enough change that it's easy to revert or alter if need be, and the performance boost is certainly welcome when it's a big battle of these sorts of ships.

Issue History

Date Modified Username Field Change
Dec 3, 2019 4:06 pm StarKelp New Issue
Dec 3, 2019 4:08 pm StarKelp File Added: EntitySimLogicImplementation.cs
Dec 3, 2019 4:08 pm StarKelp Note Added: 0054780
Dec 3, 2019 4:38 pm Chris_McElligottPark Assigned To => Chris_McElligottPark
Dec 3, 2019 4:38 pm Chris_McElligottPark Status new => resolved
Dec 3, 2019 4:38 pm Chris_McElligottPark Resolution open => fixed
Dec 3, 2019 4:38 pm Chris_McElligottPark Fixed in Version => 1.011 SuperCat Swats Back
Dec 3, 2019 4:38 pm Chris_McElligottPark Note Added: 0054782