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 { /// /// 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. /// 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 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 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 } }