using Arcen.Universal; using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using Arcen.AIW2.Core; 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 ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) return; //tends to diverge! 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! } if ( CentralVars.DEBUG_TURN_OFF_RE_EVAL_UNIT_ORDERS ) return; if ( Entity.GetIsFlagshipInStationaryMode() ) return; int debugStage = 0; try { debugStage = 10; Fleet.Membership fleetMem = Entity.FleetMembership; if ( fleetMem == null ) { //don't try to give orders to things that have no fleet membership //complain visually about the lack of fleet membership unless MP client. //if no fleet membership on the client, it will be fixed soon by sync. Otherwise the host will error and let us know. if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLog( "Blank FleetMembership for ship of type " + Entity.TypeData.DisplayName + "!", Verbosity.ShowAsError ); return; } debugStage = 20; PlanetFaction pFaction = Entity.PlanetFaction; if ( pFaction == null ) { //don't try to give orders to things that have no fleet membership //complain visually about the lack of fleet membership unless MP client. //if no fleet membership on the client, it will be fixed soon by sync. Otherwise the host will error and let us know. if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLog( "Blank PlanetFaction for ship of type " + Entity.TypeData.DisplayName + "!", Verbosity.ShowAsError ); return; } debugStage = 30; Faction faction = pFaction.Faction; if ( faction == null ) { //don't try to give orders to things that have no fleet membership //complain visually about the lack of fleet membership unless MP client. //if no fleet membership on the client, it will be fixed soon by sync. Otherwise the host will error and let us know. if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLog( "Blank Faction for ship of type " + Entity.TypeData.DisplayName + "!", Verbosity.ShowAsError ); return; } debugStage = 40; GameEntityTypeData.MarkLevelStats entityMarkData = Entity.DataForMark; if ( entityMarkData == null ) { //don't try to give orders to things that have no fleet membership //complain visually about the lack of fleet membership unless MP client. //if no fleet membership on the client, it will be fixed soon by sync. Otherwise the host will error and let us know. if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLog( "Blank DataForMark for ship of type " + Entity.TypeData.DisplayName + "!", Verbosity.ShowAsError ); return; } EntityOrderCollection entityOrders = Entity.Orders; if ( entityOrders == null ) { if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLog( "Blank Entity.Orders for ship of type " + Entity.TypeData.DisplayName + "!", Verbosity.ShowAsError ); return; } debugStage = 1000; #region tracing _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 ); #endregion debugStage = 1100; #region Self Building Checks if ( Entity.SelfBuildingMetalRemaining.RawValue > 0 ) { debugStage = 1200; //if this isn't a player ship, we never self-build anyhow. if ( faction.Type != FactionType.Player ) Entity.SelfBuildingMetalRemaining = FInt.Zero; else //okay, this does belong to the player { //this belongs to the player, but was told to self-build and is not a self-builder, then stop it! //may have died to remains and come back, or similar. Handle it with regular repairs. if ( entityMarkData.GetMetalFlow( MetalFlowPurpose.SelfConstruction ) == null ) Entity.SelfBuildingMetalRemaining = FInt.Zero; } } #endregion #region Suicide Checks For Ships After Non Human Teams if ( Entity.IsAfterANonHumanTeam_NonSim && ArcenNetworkAuthority.GetIsHostMode() /*&& Entity.PlanetFaction.Faction.SpecialFactionData.IsConsideredPartOfTheAIFactionForWhatCanOwnPurposes*/ ) { Int16 factionIndexTargeting = (Int16)Entity.CalculateFactionIndexIsChasingInsteadOfHumans(); //-1 = unset //-2 = a whole category of factions if ( factionIndexTargeting >= 0 || factionIndexTargeting == -2 ) { bool shouldBeKeptAlive = true; if ( Entity.PlanetFaction.Faction.SpecialFactionData.IsConsideredHunter && factionIndexTargeting >= 0 ) { Faction targetedFaction = World_AIW2.Instance.GetFactionByIndex( factionIndexTargeting ); if ( targetedFaction != null && targetedFaction.SpecialFactionData.HunterTargetingThisFactionDiesImmediately ) shouldBeKeptAlive = false; } //bool isLikelyToBehave = true; //if ( Entity.FireteamSpecificationOrNull == null || !Entity.FireteamSpecificationOrNull.IsActive() ) // isLikelyToBehave = false; //else //{ // if ( Entity.FireteamId < 0 ) // isLikelyToBehave = false; // else // { // Fireteam team = (Fireteam)Entity.PlanetFaction.Faction.Implementation.GetFireteamBaseById( Entity.PlanetFaction.Faction, Entity.FireteamId ); // if ( team == null || team.SpecificationOrNull == null || !team.SpecificationOrNull.IsActive() ) // isLikelyToBehave = false; // } //} if ( ( Entity.PlanetFaction.Faction.SpecialFactionData.IsConsideredWarden || Entity.PlanetFaction.Faction.SpecialFactionData.IsConsideredPraetorian )&& factionIndexTargeting >= 0 ) { Planet currentPlanet = Entity.Planet; if ( currentPlanet != null ) { bool foundOurTargetAroundHere = false; currentPlanet.DoForLinkedNeighborsAndSelf( false, delegate ( Planet p ) { if ( p == null ) return DelReturn.Continue; if ( p.UnderInfluenceOfFactionIndex.Contains( factionIndexTargeting ) ) { foundOurTargetAroundHere = true; return DelReturn.Break; } if ( p.Factions[factionIndexTargeting].Entities.SquadCount > 0 ) { foundOurTargetAroundHere = true; return DelReturn.Break; } return DelReturn.Continue; } ); if ( !foundOurTargetAroundHere ) shouldBeKeptAlive = false; } } if ( !shouldBeKeptAlive ) { int unitCount = 1 + Entity.ExtraStackedSquadsInThis; int unitStrength = Entity.GetStrengthOfSelfAndContents(); World_AIW2.Instance.KilledBecauseChasingAFactionWeAreTooFarFrom_Count += unitCount; World_AIW2.Instance.KilledBecauseChasingAFactionWeAreTooFarFrom_Strength += unitStrength; Entity.PlanetFaction.Faction.KilledBecauseChasingAFactionWeAreTooFarFrom_Count += unitCount; Entity.PlanetFaction.Faction.KilledBecauseChasingAFactionWeAreTooFarFrom_Strength += unitStrength; Entity.SetToBeRemovedAtEndOfThisFrameForReason( InstancedRendererDeactivationReason.KilledBecauseChasingAFactionWeAreTooFarFrom ); //ArcenDebugging.ArcenDebugLogSingleLine( "Kill " + Entity.PlanetFaction.Faction.GetDisplayName() + " " + Entity.TypeData.GetDisplayName() + " that was after faction " + // (factionIndexTargeting < 0 ? "a group of factions" : // World_AIW2.Instance.Factions[factionIndexTargeting].GetDisplayName() ) + " but not in a fireteam targeting them, or something to that effect.", Verbosity.DoNotShow ); } } else if ( factionIndexTargeting == -2 ) //a whole category of factions { //Chris says: to get here, the fireteam targeting must have already been present //if ( Entity.FireteamSpecificationOrNull == null ) //{ // Entity.FireteamSpecificationOrNull = new FireteamRequiredTarget(); // Entity.FireteamSpecificationOrNull.FactionIdx = factionIndexTargeting; //} } } #endregion debugStage = 1500; GameEntity_Squad guarded = Entity.Guarding.GetSquad(); debugStage = 1600; if ( guarded != null && guarded.PlanetFaction.Faction.Type != faction.Type ) { debugStage = 1700; guarded = null; Entity.GuardingOffsets.Clear(); Entity.Guarding.PrimaryKeyID = 0; Entity.Guarding.Ref = null; } debugStage = 2000; EntityOrder order = Entity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision( true ); debugStage = 2100; if ( order != null && order.TypeData != null ) { debugStage = 2110; switch ( order.TypeData.Type ) { case EntityOrderType.Unload_Transport: Entity.FleetMembership.Fleet.IsFleetInTransportLoadMode = false; //switch to unload mode! return; case EntityOrderType.SetBehavior_Stationary: Entity.GetEffectiveOrders().SetBehaviorDirectlyInSim( EntityBehaviorType.Stationary, -1 ); return; case EntityOrderType.SetBehavior_Attacker_Full: Entity.GetEffectiveOrders().SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, -1 ); return; case EntityOrderType.SetBehavior_Attacker_PursueOnlyInRange: Entity.GetEffectiveOrders().SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_PursueOnlyInRange, -1 ); return; case EntityOrderType.SetBehavior_StopToShootAnySeenTargets_On: Entity.StopToShootAnySeenTargets = true; return; case EntityOrderType.SetBehavior_StopToShootAnySeenTargets_Off: Entity.StopToShootAnySeenTargets = false; return; } } EntityBehaviorType effectiveBehavior = Entity.GetEffectiveOrders().Behavior; debugStage = 2200; #region tracing if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "effectiveBehavior = Entity.GetEffectiveOrders().Behavior=" ).Add( effectiveBehavior.ToString() ); #endregion debugStage = 2300; if ( Entity.Guarding.Ref == null && Entity.GuardingOffsets.Count > 0 && order != null && (order.Source == OrderSource.HumanPlayer || order.TypeData.Type == EntityOrderType.GetIntoTransport) && GetShouldEntityUseImplicitMeleeAttackerBehavior( Entity, faction, entityOrders ) ) { debugStage = 2400; Entity.GuardingOffsets.Clear(); #region tracing if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "implicit melee attacker with order from human, suppressing guard offset to avoid going back to the remembered point after the order" ); #endregion } debugStage = 2500; if ( effectiveBehavior != EntityBehaviorType.Attacker_PursueOnlyInRange ) { debugStage = 2600; if ( order != null && order.ShouldOverrideBehavior ) { #region tracing 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(); } #endregion return; } debugStage = 2700; //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 ) { #region tracing if ( _trace ) { traceBuffer.Add( "Obeying order from player which overrides behaviour" ); ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow ); traceBuffer.Clear(); } #endregion return; } } debugStage = 3000; if ( GetShouldEntityUseImplicitMeleeAttackerBehavior( Entity, faction, entityOrders ) ) { debugStage = 3100; effectiveBehavior = EntityBehaviorType.Attacker_PursueOnlyInRange; debugStage = 3200; //if ( Entity.Guarding.Ref == null && Entity.GuardingOffsets.Count <= 0 ) //{ // Entity.GuardingOffsets.Add( Entity.WorldLocation ); // so the melee unit returns to its original place when done // #region tracing // if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "implicit melee attacker, adding guard offset to return to" ); // #endregion //} #region tracing if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "effectiveBehavior = " ).Add( effectiveBehavior.ToString() ).Add( " because Entity.TypeData.MeleeRange && effectiveBehavior == EntityBehaviorType.None && !Entity.IsInHoldFireMode && pFaction != null && faction.Type == FactionType.Player" ); #endregion } debugStage = 4000; switch ( effectiveBehavior ) { case EntityBehaviorType.Guard_FleetShip: case EntityBehaviorType.Guard_Guardian_Patrolling: case EntityBehaviorType.Guard_Guardian_Anchored: { debugStage = 5000; bool isFreeingFromAggro = false; GameEntity_Squad guardSquad = Entity.Guarding.GetSquad(); debugStage = 5100; if ( guardSquad != null ) { debugStage = 5200; if ( guardSquad.LastTimeTakenDamageFromBeingShotByAnyone > 0 ) { //aggro me because guard was aggro'd if ( Entity.LastTimeTakenDamageFromBeingShotByAnyone < guardSquad.LastTimeTakenDamageFromBeingShotByAnyone ) Entity.LastTimeTakenDamageFromBeingShotByAnyone = guardSquad.LastTimeTakenDamageFromBeingShotByAnyone; isFreeingFromAggro = true; } else if ( Entity.LastTimeTakenDamageFromBeingShotByAnyone > 0 ) { //aggro guard because I was aggro'd if ( Entity.LastTimeTakenDamageFromBeingShotByAnyone > guardSquad.LastTimeTakenDamageFromBeingShotByAnyone ) guardSquad.LastTimeTakenDamageFromBeingShotByAnyone = Entity.LastTimeTakenDamageFromBeingShotByAnyone; isFreeingFromAggro = true; } } else if ( Entity.LastTimeTakenDamageFromBeingShotByAnyone > 0 ) isFreeingFromAggro = true; debugStage = 5500; if ( isFreeingFromAggro ) { entityOrders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "isFreeingFromAggro" ); Entity.GuardingOffsets.Clear(); entityOrders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, -1 ); //works fine since part of sim effectiveBehavior = EntityBehaviorType.Attacker_Full; } } break; } debugStage = 6000; switch ( effectiveBehavior ) { case EntityBehaviorType.Guard_FleetShip: debugStage = 7000; if ( Entity.TypeData.CannotBeStoredInsideGuardPost ) { debugStage = 7100; effectiveBehavior = EntityBehaviorType.Attacker_Full; #region tracing if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because CannotBeStoredInsideGuardPost" ); #endregion } else { debugStage = 7200; if ( faction.Type == FactionType.Player ) { debugStage = 7300; if ( pFaction.DataByStance[FactionStance.Hostile].TotalStrengthIncludingNonMilitary > 0 ) { effectiveBehavior = EntityBehaviorType.Attacker_Full; #region tracing if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because player-side and Hostile is present" ); #endregion } } else { debugStage = 7400; ShortRangePlanning_StrengthData_PlanetFaction_Stance hostileData = pFaction.DataByStance[FactionStance.Hostile]; if ( hostileData.SecondsSinceHadAnyStrength >= 0 && hostileData.SecondsSinceHadAnyStrength < ExternalConstants.Instance.ResidualAlertSeconds ) { effectiveBehavior = EntityBehaviorType.Attacker_Full; #region tracing 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)" ); #endregion } } } break; case EntityBehaviorType.Guard_Guardian_Patrolling: { debugStage = 8000; ShortRangePlanning_StrengthData_PlanetFaction_Stance hostileData = pFaction.DataByStance[FactionStance.Hostile]; debugStage = 8100; int secondsSinceLastHostilePresence = hostileData.SecondsSinceHadAnyStrength; debugStage = 8200; if ( secondsSinceLastHostilePresence >= 0 && secondsSinceLastHostilePresence < ExternalConstants.Instance.ResidualAlertSeconds ) { debugStage = 8300; bool isFreeing = false; Int16 freeingAainstFactionIndex = -1; if ( Entity.HullPointsLost > 0 || Entity.ShieldPointsLost > 0 ) isFreeing = true; else { debugStage = 8400; // 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" ); debugStage = 8500; for ( int i = 0; i < Entity.Planet.Factions.Count; i++ ) { debugStage = 8600; PlanetFaction otherFaction = Entity.Planet.Factions[i]; debugStage = 8700; if ( !pFaction.GetIsHostileTowards( otherFaction ) ) continue; debugStage = 8800; otherFaction.Entities.DoForEntities( DelegateHelper_CheckForGuardFreeingProvocation ); debugStage = 8900; if ( DelegateHelper_CheckForGuardFreeingProvocation_foundHit != null ) { debugStage = 8990; isFreeing = true; freeingAainstFactionIndex = DelegateHelper_CheckForGuardFreeingProvocation_foundHit.PlanetFaction.Faction.FactionIndex; break; } } DelegateHelper_CheckForGuardFreeingProvocation_foundHit = null; } debugStage = 9000; if ( isFreeing ) { debugStage = 9100; effectiveBehavior = EntityBehaviorType.Attacker_Full; //if ( hostileData.MobileStrength > FInt.Zero ) { debugStage = 9200; entityOrders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "isFreeing" ); Entity.GuardingOffsets.Clear(); entityOrders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, freeingAainstFactionIndex ); //works fine because sim } } } } break; } debugStage = 11000; #region tracing if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "effectiveBehavior=" ).Add( effectiveBehavior.ToString() ); #endregion debugStage = 11100; switch ( effectiveBehavior ) { case EntityBehaviorType.Attacker_Full: debugStage = 11200; this.AttackerLogic( Context, Entity, order, guarded, false, false, fleetMem, entityMarkData, faction, entityOrders ); break; case EntityBehaviorType.Attacker_PursueOnlyInRange: debugStage = 11300; this.AttackerLogic( Context, Entity, order, guarded, true, true, fleetMem, entityMarkData, faction, entityOrders ); break; //case EntityBehaviorType.RallyToPlayerFleet: // this.FleetRallyLogic( Context, Entity, order ); // break; case EntityBehaviorType.Guard_FleetShip: debugStage = 11400; this.Guard_FleetShipLogic( Context, Entity, order, guarded, fleetMem, entityMarkData, faction, pFaction, entityOrders ); break; case EntityBehaviorType.Guard_Guardian_Patrolling: debugStage = 11500; this.Guard_Guardian_PatrollingLogic( Context, Entity, order, guarded, fleetMem, entityMarkData, faction, pFaction, entityOrders ); break; case EntityBehaviorType.Guard_Guardian_Anchored: debugStage = 11600; this.Guard_Guardian_AnchoredLogic( Context, Entity, order, guarded, fleetMem, entityMarkData, faction, pFaction, entityOrders ); break; default: debugStage = 11700; this.AssisterLogic_Stationary( Context, Entity, order, guarded, fleetMem, entityMarkData, entityOrders ); break; } #region tracing if ( _trace ) { ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow ); traceBuffer.Clear(); } #endregion } catch ( Exception e ) { if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLogSingleLine( "ReevaluateUnitOrders debugStage: " + debugStage + " \n" + e, Verbosity.ShowAsError ); } } public bool GetShouldEntityUseImplicitMeleeAttackerBehavior( GameEntity_Squad Entity, Faction faction, EntityOrderCollection entityOrders ) { return Entity.TypeData.MeleeRange && (entityOrders.Behavior == EntityBehaviorType.None || entityOrders.Behavior == EntityBehaviorType.Stationary ) && !Entity.IsInHoldFireMode && faction.Type == FactionType.Player; } #endregion #region AttackerLogic private void AttackerLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool AllowOverridingHumanOrders, bool OnlyInRange, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, Faction faction, EntityOrderCollection entityOrders ) { if ( guarded != null && Entity.TypeData.IsReinforcementLocation ) { // 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 entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "StoppingGuard" ); 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.TypeData.IsMobileFleetFlagship && !fleetMem.Fleet.IsFleetFlagshipAllowedToUseMovementModes ) return; //flagships are only allowed to use this logic if the user has requested it if ( Entity.TypeData.HasAnyMetalFlows && entityMarkData.AssistRange > 0 && (!Entity.TypeData.IsCombatant || Entity.TypeData.IsCombatantDespiteNoWeapons ) ) { this.AttackerLogic_Assister( Context, Entity, order, guarded, OnlyInRange, fleetMem, entityMarkData, entityOrders ); } else { this.AttackerLogic_Combat( Context, Entity, order, guarded, AllowOverridingHumanOrders, fleetMem, entityMarkData, faction, entityOrders ); } } #endregion #region AssisterLogic_Stationary private void AssisterLogic_Stationary( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, EntityOrderCollection entityOrders ) { if ( Entity.TypeData.HasAnyMetalFlows && entityMarkData.AssistRange > 0 && (!Entity.TypeData.IsCombatant || Entity.TypeData.IsCombatantDespiteNoWeapons) ) { this.AttackerLogic_Assister( Context, Entity, order, guarded, true, fleetMem, entityMarkData, entityOrders ); } } #endregion #region AttackerLogic_Assister private void AttackerLogic_Assister( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool OnlyInRange, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, EntityOrderCollection entityOrders ) { if ( Entity == null ) return; if ( Entity.TypeData.IsFleetLeader ) return; bool needNewOrder = order == null; if ( order != null ) { switch ( order.TypeData.Type ) { case EntityOrderType.Move_Normal: case EntityOrderType.Wormhole: needNewOrder = false; break; } } //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 = entityMarkData.AssistRange; GameEntity_Squad bestTarget = null; int bestDistance = 0; int highestPriorityOfFlow = 0; for ( int i = 0; i < Entity.FramePlan_PlannedFlows.Count; i++ ) { PlannedMetalFlow flow = Entity.FramePlan_PlannedFlows[i]; if ( highestPriorityOfFlow >= flow.PriorityLevel ) continue; highestPriorityOfFlow = flow.PriorityLevel; } for ( int i = 0; i < Entity.FramePlan_PlannedFlows.Count; i++ ) { PlannedMetalFlow flow = Entity.FramePlan_PlannedFlows[i]; if(flow.PriorityLevel < highestPriorityOfFlow) continue; 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; return; } if ( !OnlyInRange ) //if only does stuff in range, don't look at this since it's out of range { if ( bestTarget != null && bestDistance <= distance ) continue; bestTarget = target; bestDistance = distance; } } if ( bestTarget == null ) { //only check for factories now for ( int i = 0; i < Entity.FramePlan_PlannedFlows.Count; i++ ) { PlannedMetalFlow flow = Entity.FramePlan_PlannedFlows[i]; switch ( flow.Purpose ) { case MetalFlowPurpose.FactoryConstructionForPlayerMobileFleets: 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; return; } if ( !OnlyInRange ) //if only does stuff in range, don't look at this since it's out of range { 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 = ( entityMarkData.AssistRange * 8 ) / 10; targetPoint = Entity.WorldLocation.GetPointAtAngleAndDistance( angleToTarget, closeToWithinRange ); } if ( targetPoint == ArcenPoint.ZeroZeroPoint ) return; //this would be an invalid order entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "AutomatedAssister" ); EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other ); if ( newOrder == null ) return; entityOrders.QueueOrder( Entity, newOrder ); if ( _trace ) { if ( bestTarget != null ) traceBuffer.Add( Environment.NewLine ).Add( "picked assist target" ).Add( bestTarget.TypeData.InternalName ); else traceBuffer.Add( Environment.NewLine ).Add( "no assist target, going back to targetPoint " ).Add( targetPoint.ToString() ); } } #endregion #region AttackerLogic_Combat private void AttackerLogic_Combat( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool AllowOverridingHumanOrders, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, Faction faction, EntityOrderCollection entityOrders ) { 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; entityOrders.RemoveQueuedOrder( order, true ); order = null; } else { //Entity.DebugText += " existingTarget: " + existingTarget.TypeData.InternalName; } } if ( order == null ) needNewOrder = true; 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; EntitySystemTypeData typeData; for ( int i = 0; i < Entity.Systems.Count; i++ ) { system = Entity.Systems[i]; typeData = system.TypeData; if ( typeData == null ) continue; if ( system.CurrentFRDTarget.PrimaryKeyID > 0 ) { if ( entityMarkData.HasAnySniperRangedWeapons ) { if ( !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: " + bestTarget.TypeData.InternalName; if ( !insertOrderInsteadOfClear ) entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "AutomatedCombat" ); EntityOrder newOrder = EntityOrder.Create_Attack( Entity, bestTarget.PrimaryKeyID, false, OrderSource.Other ); if ( newOrder == null ) return; //some sort of problem, probably the entity is dead! newOrder.CalculateStrengthCountingData( Entity, true, true, true ); if ( insertOrderInsteadOfClear ) entityOrders.InsertOrderAtStart( Entity, newOrder ); else entityOrders.QueueOrder( Entity, newOrder ); if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "picked target" ).Add( bestTarget.TypeData.InternalName ); } else { //Entity.DebugText += " bestTarget null!"; if ( !GetShouldEntityUseImplicitMeleeAttackerBehavior( Entity, faction, entityOrders ) ) { if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no order becasue bestTarget == null && !GetShouldEntityUseImplicitMeleeAttackerBehavior( Entity )" ); return; } if ( Entity.GuardingOffsets.Count <= 0 ) { if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no order despite implicit melee behavior, becasue bestTarget == null && Entity.GuardingOffsets.Count <= 0" ); return; } ArcenPoint targetPoint = Entity.GuardingOffsets[0]; if ( targetPoint == ArcenPoint.ZeroZeroPoint ) { if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no order despite implicit melee behavior and Entity.GuardingOffsets.Count > 0, because the first point is 0,0, which is invalid" ); return; } entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "AutomatedCombatMove" ); EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other ); if ( newOrder == null ) { if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no order despite implicit melee behavior and valid Entity.GuardingOffsets[0], because EntityOrder.Create_Move_Normal returned null. Null! The nerve." ); return; } entityOrders.QueueOrder( Entity, newOrder ); if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no bestTarget, so because of implicit melee behavior, going back to targetPoint " ).Add( targetPoint.ToString() ); } } #endregion #region Guard_FleetShipLogic private void Guard_FleetShipLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, Faction faction, PlanetFaction pFaction, EntityOrderCollection entityOrders ) { if ( guarded == null || guarded.ToBeRemovedAtEndOfThisFrame || guarded.HasBeenRemovedFromSim || guarded.Planet != Entity.Planet ) { if ( !Entity.TypeData.CannotBeStoredInsideGuardPost && Entity.TypeData.IsDrone ) { 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" ); entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent, "GuardGoingAttackerBecauseGuardedIsGone" ); Int16 bestFactionIndex = pFaction.GetIndexOfMostAnnoyingFaction( Context, AnnoyingFactionFlags.DefaultToRandomNonHumanHostileFaction ); entityOrders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, bestFactionIndex ); //works fine because sim } } else { if ( !Entity.TypeData.CannotBeStoredInsideGuardPost && 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, "AbsorbGuard", Entity.PlanetFaction.Faction.SpecialFactionData.InternalName ); Entity.AddOrSetExtraStackedSquadsInThis( 0, true ); //without this, it will just remove one from the stack and exponentially get more because of it! Entity.SetToBeRemovedAtEndOfThisFrameForReason( InstancedRendererDeactivationReason.GettingIntoTransport ); } 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" ); entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent, "GuardGettingCloserToGuarded" ); EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, guarded.WorldLocation, false, OrderSource.Other ); if ( newOrder == null ) return; entityOrders.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, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, Faction faction, PlanetFaction pFaction, EntityOrderCollection entityOrders ) { if ( Entity == null || Entity.Planet == null ) return; int debugStage = 1; try { debugStage = 100; if ( Entity.Planet != null ) { debugStage = 110; switch ( Entity.Planet.BattleStatus ) { case PlanetBattleStatus.Tier3_PlayersAbsent_OffFrame: case PlanetBattleStatus.Tier3_PlayersAbsent_OnFrame: return; //if on a tier 3 planet (no player looking at me, no player ships), then NEVER bother with patrolling guardians. It's a waste } } debugStage = 200; if ( guarded == null || Entity.GuardingOffsets.Count <= 0 ) { guarded = null; debugStage = 300; Entity.GuardingOffsets.Clear(); Entity.Guarding.PrimaryKeyID = 0; Entity.Guarding.Ref = null; debugStage = 340; entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent, "GuardedDeadSoGuardGoingAttacker" ); debugStage = 360; Int16 bestFactionIndex = pFaction.GetIndexOfMostAnnoyingFaction( Context, AnnoyingFactionFlags.DefaultToRandomNonHumanHostileFaction ); debugStage = 380; entityOrders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, bestFactionIndex ); //works fine because sim } else { debugStage = 400; bool needNewOrder = false; if ( Entity.NextGuardingOffsetIndex >= Entity.GuardingOffsets.Count ) Entity.NextGuardingOffsetIndex = 0; debugStage = 420; ArcenPoint offset = Entity.GuardingOffsets[Entity.NextGuardingOffsetIndex]; debugStage = 440; ArcenPoint targetPoint = guarded.WorldLocation + offset; debugStage = 460; if ( order == null ) { debugStage = 500; if ( !Entity.GetIsWithinRangeOf_VeryBasicCheckOnly( targetPoint, ExternalConstants.Instance.EXTRA_SPACE_FOR_MOVEMENT_ORDERS_BETWEEN_SHIPS ) ) needNewOrder = true; } else { debugStage = 510; if ( order.RelatedSquad.PrimaryKeyID != guarded.PrimaryKeyID && !order.RelatedPoint.GetHasAnyChanceOfBeingInRange( targetPoint, ExternalConstants.Instance.EXTRA_SPACE_FOR_MOVEMENT_ORDERS_BETWEEN_SHIPS ) ) needNewOrder = true; } if ( needNewOrder ) { debugStage = 600; entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent, "GuardGoingToPatrol" ); debugStage = 610; EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other ); if ( newOrder == null ) return; debugStage = 620; entityOrders.QueueOrder( Entity, newOrder ); } else { debugStage = 700; if ( Entity.GuardingOffsets.Count > 1 && order == null ) Entity.NextGuardingOffsetIndex++; } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "Guard_Guardian_PatrollingLogic debugStage: " + debugStage + " \n" + e, Verbosity.ShowAsError ); } } #endregion #region Guard_Guardian_AnchoredLogic private void Guard_Guardian_AnchoredLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, Fleet.Membership fleetMem, GameEntityTypeData.MarkLevelStats entityMarkData, Faction faction, PlanetFaction pFaction, EntityOrderCollection entityOrders ) { if ( guarded == null ) { guarded = null; Entity.GuardingOffsets.Clear(); Entity.Guarding.PrimaryKeyID = 0; Entity.Guarding.Ref = null; entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent, "GuardWasAnchoredButGuardedDead" ); Int16 bestFactionIndex = pFaction.GetIndexOfMostAnnoyingFaction( Context, AnnoyingFactionFlags.DefaultToRandomNonHumanHostileFaction ); entityOrders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, bestFactionIndex ); //works fine because sim } } #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 ( otherfaction.Type != FactionType.Player ) // return DelReturn.Continue; //these ships are invisible to aggroing on distance! if ( otherEntity.TypeData.CannotTargetOrAlertAIReinforcementSpots ) 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, bool DoShotsAllInstaHit ) { int debugStage = 0; try { if ( CentralVars.DEBUG_TURN_OFF_SYTEM_STEP ) return; debugStage = 1000; if ( System == null ) return; debugStage = 2000; if ( System == null ) return; debugStage = 2100; EntitySystemTypeData.MarkLevelStats dataForMark = System.DataForMark; if ( dataForMark == null ) return; debugStage = 2200; if ( !dataForMark.IsFunctionalAtThisMarkLevel ) return; debugStage = 2400; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return; GameEntity_Squad parent = System.ParentEntity; if ( parent == null ) return; if ( typeData.CareAboutStateOfMatterToBeEnabled ) { if ( parent != null ) { if ( parent.CurrentStateOfMatter != typeData.MustBeThisStateOfMatterToBeEnabled ) return; } } debugStage = 3000; #region tracing bool trace = Engine_AIW2.TraceAtAll && parent == GameEntity_Base.CurrentlyHoveredOver && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ); ArcenCharacterBuffer tracingBuffer = null; if ( trace ) tracingBuffer = new ArcenCharacterBuffer(); #endregion if ( EffectiveDeltaTime <= FInt.Zero ) { #region tracing if ( !World.Instance.IsPaused ) // avoid spamming the log with useless skip messages while paused { if ( trace ) tracingBuffer.Add( "Skipping DoSystemStep for " ).Add( typeData.InternalName_Longer ).Add( " from " ).Add( parent.TypeData.InternalName ).Add( " #" ).Add( parent.PrimaryKeyID ).Add( " because EffectiveDeltaTime <= FInt.Zero" ); if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow ); } #endregion return; } else { if ( trace ) tracingBuffer.Add( "Tracing DoSystemStep for " ).Add( typeData.InternalName_Longer ).Add( " from " ).Add( parent.TypeData.InternalName ).Add( " #" ).Add( parent.PrimaryKeyID ); } debugStage = 4000; if ( 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; } debugStage = 5000; ArcenRejectionReason disabledReason = System.ForShortTermPlanning_DisabledReason; debugStage = 6000; 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; } debugStage = 7000; 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; debugStage = 8000; if ( 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; } debugStage = 9000; if ( 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; } debugStage = 10000; if ( typeData.Category == EntitySystemCategory.Weapon && 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; } debugStage = 11000; 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; } bool didSomethingThatCausesASalvoToBeMarked = false; debugStage = 12000; if ( typeData.Category == EntitySystemCategory.Weapon ) { debugStage = 13000; bool didTrigger = ActuallyFireSalvoAtTargetPriorityList( Context, System, trace, tracingBuffer, DoShotsAllInstaHit ); debugStage = 14000; 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; } else didSomethingThatCausesASalvoToBeMarked = true; } debugStage = 15000; if ( CheckForNonSalvoActivationEffects( Context, System, trace, tracingBuffer ) ) didSomethingThatCausesASalvoToBeMarked = true; debugStage = 16000; if ( didSomethingThatCausesASalvoToBeMarked ) this.MarkSalvoAsHavingBeenFired( System ); #region tracing if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow ); #endregion } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "DoSystemStep error at debugStage " + debugStage + ", error: " + e, Verbosity.ShowAsError ); } } #endregion #region DoSystemOnDeathEffect public override void DoSystemOnDeathEffect( ArcenSimContext Context, EntitySystem System ) { if ( System == null ) return; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return; if ( typeData.Category == EntitySystemCategory.Weapon ) { bool doShotsAllInstaHit = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "ShotsAllInstaHit" ); ActuallyFireSalvoFromOnDeath( Context, System, false, null, doShotsAllInstaHit ); } CheckForNonSalvoActivationEffects( Context, System, false, null ); } #endregion #region CheckForNonSalvoActivationEffects public bool CheckForNonSalvoActivationEffects( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer ) { if ( System == null ) return false; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return false; GameEntity_Squad parent = System.ParentEntity; bool didSomethingThatCausesASalvoToBeMarked = false; #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "Tracing CheckForNonSalvoActivationEffects for " ).Add( typeData.InternalName_Longer ).Add( " from " ).Add( parent?.TypeData?.InternalName ).Add( " #" ).Add( parent.PrimaryKeyID ); // no current need to trace more than the call itself, add later if needed #endregion if ( typeData.SpawnsEntity != null && parent != null ) { parent.SpawnEntity_ReturnNullIfMPClient( typeData.SpawnsEntity, parent.CurrentMarkLevel, parent.FleetMembership.Fleet, 0, //this is a secondary entity, so it doesn't ever get unique slots parent.Orders.BehaviorRelatedFactionIndex, Context ); didSomethingThatCausesASalvoToBeMarked = true; } if ( typeData.WhiteoutSeconds > 0 && parent != null ) { parent.Planet.WhiteoutTotalDuration = typeData.WhiteoutSeconds; parent.Planet.WhiteoutRemainingDuration = typeData.WhiteoutSeconds; didSomethingThatCausesASalvoToBeMarked = true; } return didSomethingThatCausesASalvoToBeMarked; } #endregion #region MarkSalvoAsHavingBeenFired private void MarkSalvoAsHavingBeenFired( EntitySystem System ) { if ( System == null ) return; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return; System.TimeUntilNextShot += (FInt)System.GetWeaponReloadTime( true ); if ( System.DataForMark.AltSecondsPerSalvo > 0 ) { System.ShotsSinceLastSwitchingSalvoFiringMode++; if ( System.IsInAltSalvoFiringMode ) { if ( System.ShotsSinceLastSwitchingSalvoFiringMode >= typeData.UseAlternateRateOfFireForXShotsBeforeReverting ) { System.IsInAltSalvoFiringMode = false; System.ShotsSinceLastSwitchingSalvoFiringMode = 0; } } else { if ( System.ShotsSinceLastSwitchingSalvoFiringMode >= typeData.UseAlternateRateOfFireAfterXShots ) { System.IsInAltSalvoFiringMode = true; System.ShotsSinceLastSwitchingSalvoFiringMode = 0; } } } } #endregion #region ActuallyFireSalvoAtTargetPriorityList public bool ActuallyFireSalvoAtTargetPriorityList( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer, bool DoShotsAllInstaHit ) { if ( System == null ) return false; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return false; int debugStage = 0; try { debugStage = 100; System.LastShotFireAbortCode = 0; //System.ParentEntity.DebugText = "TRY SALVO"; if ( typeData.OnlyFiresOnDeath ) { debugStage = 200; System.LastShotFireAbortCode = 1; return false; //no shot was fired } GameEntity_Squad systemParent = System.ParentEntity; if ( systemParent == null ) return false; GameEntityTypeData systemParentTypeData = systemParent.TypeData; if ( systemParentTypeData == null ) return false; PlanetFaction systemParentPFaction = systemParent.PlanetFaction; if ( systemParentPFaction == null ) return false; Faction systemParentFaction = systemParentPFaction.Faction; if ( systemParentFaction == null ) return false; EntityOrderCollection systemParentOrders = systemParent.Orders; if ( systemParentOrders == null ) return false; debugStage = 1000; int cloakingPointsIfMobileNPC = systemParentTypeData.IsMobile && systemParentFaction.Type != FactionType.Player ? systemParent.GetCurrentCloakingPoints() : 0; debugStage = 1100; EntityOrder order = systemParent.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision( false ); debugStage = 1200; GameEntity_Squad frdAttackTarget = System.CurrentFRDTarget.GetSquad(); GameEntity_Squad mainAttackTarget = null; debugStage = 1300; if ( order != null && order.TypeData.Type == EntityOrderType.Attack ) mainAttackTarget = order.RelatedSquad.GetSquad(); else //no attack order at the moment { debugStage = 1400; 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. } } debugStage = 2000; if ( mainAttackTarget != null ) { debugStage = 2100; bool isInvalid = false; try { isInvalid = mainAttackTarget.GetHasBeenDestroyed() || mainAttackTarget.HasBeenRemovedFromSim || mainAttackTarget.SecondsSpentAsRemains > 0 || mainAttackTarget.ToBeRemovedAtEndOfThisFrame || mainAttackTarget.HasNotYetBeenFullyClaimed || mainAttackTarget.PlanetFaction == null || mainAttackTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject; } catch { isInvalid = true; } if ( isInvalid ) { debugStage = 2200; mainAttackTarget = null; systemParentOrders.RemoveQueuedOrder( order, true ); } } debugStage = 3000; if ( frdAttackTarget != null ) { debugStage = 3100; if ( frdAttackTarget.GetHasBeenDestroyed() || frdAttackTarget.HasBeenRemovedFromSim || frdAttackTarget.SecondsSpentAsRemains > 0 || frdAttackTarget.ToBeRemovedAtEndOfThisFrame || frdAttackTarget.HasNotYetBeenFullyClaimed || frdAttackTarget.PlanetFaction == null || frdAttackTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject ) { debugStage = 3200; frdAttackTarget = null; System.CurrentFRDTarget.SetInternalRef( null ); } } //systemParent.DebugText += " SHOOT?"; debugStage = 4000; if ( mainAttackTarget == null && frdAttackTarget == null && (System.targetPriorityList == null || System.targetPriorityList.Count == 0) ) { debugStage = 4100; if ( System != null ) System.LastShotFireAbortCode = 3; return false; //no shot was fired } debugStage = 4200; bool chooseNewFRDIfPossible = (mainAttackTarget == null && frdAttackTarget == null && systemParentOrders.Behavior == EntityBehaviorType.Attacker_Full); debugStage = 4300; bool chooseNewAttackMoveIfPossible = (mainAttackTarget == null && frdAttackTarget == null && systemParentOrders.Behavior == EntityBehaviorType.Attacker_PursueOnlyInRange); #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "Tracing ActuallyFireSalvo for " ).Add( typeData.InternalName_Longer ).Add( " from " ).Add( systemParentTypeData.InternalName ).Add( " #" ).Add( systemParent.PrimaryKeyID ); #endregion debugStage = 5000; int totalShotsToFire = System.DataForMark.ShotsPerSalvo; debugStage = 5100; if ( systemParent.ExtraStackedSquadsInThis >= ExternalConstants.Instance.Balance_StacksPerBonusShot ) { debugStage = 5200; int extraShotsMultiplier = 1 + (systemParent.ExtraStackedSquadsInThis / ExternalConstants.Instance.Balance_StacksPerBonusShot); totalShotsToFire *= extraShotsMultiplier; } debugStage = 5300; ArcenPoint originPoint = System.GetWorldLocation(); int spacingMultiplier = 40; int withinSalvoLowEnd = 5 * spacingMultiplier; int withinSalvoHighEnd = 10 * spacingMultiplier; debugStage = 5400; Planet myPlanet = systemParent.Planet; //systemParent.DebugText += " SHOOT!"; bool hasPlayedSoundYet = false; //int runningTargetIndex = 0; int numberOfShotsFired = 0; bool hadAnyOutOfRange = false; int startingTargetIndex = 0; int currentActualTargetIndex = -2; LazyLoadSquadWrapper SquadSwapper; bool alreadyFiredAtMainAttackTarget = false; bool alreadyFiredAtFRDAttackTarget = 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 debugStage = 6000; FInt runningDelay = FInt.Zero; for ( int outerLoop = 0; outerLoop <= 1; outerLoop++ ) { debugStage = 6100; //do two passes through. The first pass, only fire at things that will not be overkilled. //if no targets found, then fire even on things that will be overkilled. bool careAboutOverkill = (outerLoop == 0); if ( outerLoop > 0 ) { debugStage = 6200; //THAT said, if the system takes more than 10 seconds between shots, then skip that overkill bit afterward, as that can be too wasteful. if ( System.GetWeaponReloadTime( false ) > 10 ) break; //If we've already fired upon every target we could skip the 2nd loop entirely. We'd be checking nothing anyway. if ( startingTargetIndex >= System.targetPriorityList.Count ) break; } int targetIndex = -2; debugStage = 7000; int attemptCount = 100; while ( numberOfShotsFired < totalShotsToFire && targetIndex < System.targetPriorityList.Count && attemptCount-- > 0 ) { debugStage = 7100; //systemParent.DebugText += " " + targetIndex; #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetIndex " ).Add( targetIndex ); #endregion GameEntity_Squad potentialTarget; { debugStage = 7200; if ( System.targetPriorityList.Count <= targetIndex ) break; debugStage = 7300; if ( targetIndex == -2 ) //try to attack the main focus of this entity if it has orders { debugStage = 7400; if ( mainAttackTarget == null || alreadyFiredAtMainAttackTarget) { targetIndex++; continue; } potentialTarget = mainAttackTarget; currentActualTargetIndex = -2; #region tracing if ( trace ) { tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "autoTarget=mainAttackTarget" ).Add( ":" ).Add( potentialTarget?.TypeData.InternalName ?? "null" ); } #endregion } else if ( targetIndex == -1 ) //try to attack the FRD focus of this system { debugStage = 7500; if ( frdAttackTarget == null || alreadyFiredAtFRDAttackTarget || frdAttackTarget == mainAttackTarget ) { targetIndex++; //can't attack the same thing more than once continue; } potentialTarget = frdAttackTarget; currentActualTargetIndex = -1; #region tracing if ( trace ) { tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "autoTarget=frdAttackTarget" ).Add( ":" ).Add( potentialTarget?.TypeData.InternalName ?? "null" ); } #endregion } else { if(outerLoop > 0 && targetIndex == 0)//if this is the start of the 2nd outer loop, but the first time iterating over the actual target list, skip ahead if necessary { targetIndex = startingTargetIndex; } debugStage = 7600; 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(); currentActualTargetIndex = targetIndex; if ( potentialTarget == null || potentialTarget == frdAttackTarget || potentialTarget == mainAttackTarget ) { targetIndex++; //can't attack the same thing more than once continue; } #region tracing if ( trace ) { tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "autoTarget=System.targetPriorityList[targetIndex].GetSquad()" ).Add( ":" ).Add( potentialTarget?.TypeData.InternalName ?? "null" ); } #endregion } debugStage = 8000; //systemParent.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 ); } #endregion debugStage = 8100; bool isInvalid = false; try { isInvalid = potentialTarget == null || potentialTarget.HasBeenRemovedFromSim || potentialTarget.ToBeRemovedAtEndOfThisFrame || potentialTarget.SecondsSpentAsRemains > 0 || potentialTarget.GetHasBeenDestroyed() || potentialTarget.HasNotYetBeenFullyClaimed || potentialTarget.PlanetFaction == null || potentialTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject; } catch { isInvalid = true; } if ( isInvalid ) { debugStage = 8200; //if ( potentialTarget != null )//&& potentialTarget.TypeData.InternalName.Contains( "Spider" ) ) // systemParent.DebugText += potentialTarget.HasBeenRemovedFromSim + " " + potentialTarget.ToBeRemovedAtEndOfThisFrame + " " + potentialTarget.SecondsSpentAsRemains + " " + potentialTarget.TypeData.InternalName; //else // systemParent.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; } debugStage = 8300; if ( potentialTarget.Planet != myPlanet ) { debugStage = 8400; //systemParent.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! " + 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; } debugStage = 8500; if ( careAboutOverkill ) { debugStage = 8600; //if it would be overkilled and it's not under a shield, ignore it for now. if ( !potentialTarget.GetIsProtectedByAnyForcefield() && 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; } } debugStage = 9000; //if the target should be unattackable by the weapon, ignore it for now. if ( potentialTarget.TypeData.IsMobile && typeData.OnlyTargetsStaticUnits || !potentialTarget.TypeData.IsMobile && typeData.OnlyTargetsMobileUnits ) { debugStage = 9100; 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; } debugStage = 9200; //if the target is invulnerable, ignore for now. isInvalid = false; try { isInvalid = (potentialTarget.CountOfEntitiesProvidingExternalInvulnerability > 0 && potentialTarget.CountOfEntitiesProvidingExternalInvulnerability >= potentialTarget.TypeData.ExternalInvulnerabilityUnitRequiredCount) || potentialTarget.TypeData.ImmuneToAllDamage || potentialTarget.Debug_IgnoresDamage; } catch { isInvalid = true; } if ( isInvalid ) { debugStage = 9300; targetIndex++; continue; } debugStage = 10100; //if there are any damage modifiers, and any of them would result in a 0x damage, ignore for now. if ( typeData.OutgoingDamageModifiers_FullList.Count > 0 ) { debugStage = 10200; bool skip = false; bool excludeFromTargets = false; DamageModifier mod; int mark = systemParent.CurrentMarkLevel; debugStage = 10300; for ( int i = 0; i < typeData.OutgoingDamageModifiers_FullList.Count; i++ ) { debugStage = 10400; mod = typeData.OutgoingDamageModifiers_FullList[i]; debugStage = 10500; if ( mod.MultiplierForMark[mark] == FInt.Zero && mod.CalculateDoesMeetCriteria( potentialTarget, systemParent ) ) { debugStage = 10600; skip = true; switch ( mod.BasedOn )//if the damage modifier is based on a static property (that is to say there is NO WAY this type will ever NOT apply), exclude it from the target list { case DamageModifierBasedOn.Albedo: case DamageModifierBasedOn.Armor_mm: case DamageModifierBasedOn.EnergyUsage: case DamageModifierBasedOn.Engine_gx: case DamageModifierBasedOn.Mass_tX: excludeFromTargets = true; break; } break; } } debugStage = 10700; if ( skip ) { debugStage = 1080; if ( excludeFromTargets && targetIndex >= 0 ) System.targetPriorityList.RemoveAt( targetIndex ); //don't increment targetIndex if was an actual target else targetIndex++; continue; } } targetIndex++; } debugStage = 11100; //the first one is probably the best if ( chooseNewFRDIfPossible ) { debugStage = 11200; System.CurrentFRDTarget.SetInternalRef( potentialTarget ); System.targetPriorityList.RemoveAt( currentActualTargetIndex ); currentActualTargetIndex = -2;//make it invalid for swapping later on chooseNewFRDIfPossible = false; } debugStage = 11300; if ( !System.GetIsTargetInRange( potentialTarget, RangeCheckType.ForActualFiring ) ) { debugStage = 11400; hadAnyOutOfRange = true; //systemParent.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! } debugStage = 11500; //the first one is probably the best -- difference is MUST be in range if ( chooseNewAttackMoveIfPossible ) { debugStage = 11600; System.CurrentFRDTarget.SetInternalRef( potentialTarget ); System.targetPriorityList.RemoveAt( currentActualTargetIndex ); currentActualTargetIndex = -1;//make it invalid for swapping later on chooseNewAttackMoveIfPossible = false; } numberOfShotsFired++; debugStage = 12100; //systemParent.DebugText += " FIRE!"; InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, potentialTarget, originPoint, Context, System, trace, tracingBuffer, withinSalvoLowEnd, withinSalvoHighEnd, 0, false, DoShotsAllInstaHit ); debugStage = 12200; if ( outerLoop == 0 ) { if ( currentActualTargetIndex >= 0 ) { if ( startingTargetIndex != currentActualTargetIndex ) { debugStage = 12210; //Swap the target fired upon to the front section of the targets so it can be skipped in the 2nd loop SquadSwapper = System.targetPriorityList[startingTargetIndex]; System.targetPriorityList[startingTargetIndex] = System.targetPriorityList[currentActualTargetIndex]; System.targetPriorityList[currentActualTargetIndex] = SquadSwapper; } startingTargetIndex++; } else if(currentActualTargetIndex == -2)//if the main attack target was fired upon, skip it for loop 2 { alreadyFiredAtMainAttackTarget = true; } else if(currentActualTargetIndex == -1 )//if the main attack target was fired upon, skip it for loop 2 { alreadyFiredAtFRDAttackTarget = true; } } } //end while looop } //end outerloop debugStage = 13100; if ( numberOfShotsFired > 0 ) { debugStage = 13200; 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); //} debugStage = 13300; if ( systemParent.GetMaxCloakingPoints() > 0 ) { debugStage = 13400; if ( systemParent.GetCurrentCloakingPoints() > 0 ) { debugStage = 13500; int pointsLost = (systemParent.GetMaxCloakingPoints() * typeData.CloakingPercentLossFromFiring).GetNearestIntPreferringHigher(); if ( pointsLost < 1 ) pointsLost = 1; systemParent.CloakingPointsLost += pointsLost; } debugStage = 13600; systemParent.GameSecondOfLastCloakingPointLoss = World_AIW2.Instance.GameSecond; } debugStage = 14100; // Puffin thing. This is like the normal self damage, but it has no connection to the damage actually done by the system. // It instead deals a percentage of the units total health as damage each time it even fires. Intended for Minefields, Railpods, so on. if ( systemParentTypeData.HealthChangeByMaxHealthDividedByThisPerAttack != FInt.Zero ) { debugStage = 14200; if ( systemParentTypeData.HealthChangeByMaxHealthDividedByThisPerAttack < FInt.Zero ) { debugStage = 14300; int selfDamage = ((systemParent.GetMaxHullPoints() + systemParent.GetMaxShieldPoints()) / -systemParentTypeData.HealthChangeByMaxHealthDividedByThisPerAttack).IntValue; systemParent.TakeDamageDirectly( selfDamage, null, null, DamageSource.SelfDamageFromMyOwnWeapons, Context ); if ( systemParentTypeData.CustomRepairImpossibleForSecondsAfterDamagedByEnemy_Max15 > 0 ) systemParent.RepairImpossibleForSeconds = (byte)systemParentTypeData.CustomRepairImpossibleForSecondsAfterDamagedByEnemy_Max15; else systemParent.RepairImpossibleForSeconds = (byte)ExternalConstants.Instance.Balance_RepairImpossibleForSecondsAfterDamagedByEnemy; } else { debugStage = 14500; int selfHealing = ((systemParent.GetMaxHullPoints() + systemParent.GetMaxShieldPoints()) / systemParentTypeData.HealthChangeByMaxHealthDividedByThisPerAttack).IntValue; selfHealing -= systemParent.TakeHullRepair( selfHealing ); if ( selfHealing > 0 ) systemParent.TakeShieldRepair( selfHealing ); } } return true; } else //no shot was fired! { debugStage = 15100; if ( hadAnyOutOfRange ) System.LastShotFireAbortCode = 4; else System.LastShotFireAbortCode = 5; debugStage = 15200; //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; } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "ActuallyFireSalvoAtTargetPriorityList error at debugStage " + debugStage + ", error: " + e, Verbosity.ShowAsError ); return false; } } #region ActuallyFireSalvoFromOnDeath public void ActuallyFireSalvoFromOnDeath( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer, bool DoShotsAllInstaHit ) { 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, 0, false, DoShotsAllInstaHit ); } #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, int OverridingDamage, bool IsReturnFireShot, bool DoShotsAllInstaHit ) { int debugStage = 0; try { if ( System == null ) return; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return; if ( target == null ) return; GameEntity_Squad systemParent = System.ParentEntity; if ( systemParent == null ) return; Planet systemParentPlanet = systemParent.Planet; if ( systemParentPlanet == null ) return; debugStage = 1000; if ( target != null && target.AreaBoosters_ShotEvaluated.Count > 0 ) { debugStage = 1100; foreach ( var ship in target.AreaBoosters_ShotEvaluated ) { if ( ship == null ) continue; if ( ship.TypeData.DoesAttractShotsAgainstAllies ) //if protected by a ship that attracts in shots, then make the shot go to that target { target = ship; break; } } } debugStage = 1500; bool doInstaHit = false; if ( DoShotsAllInstaHit ) doInstaHit = true; debugStage = 2000; #region Check To See If Relevant: On Background Planets, Shots Should Insta-Hit And Not Spawn Entities if ( !doInstaHit && systemParent != null && systemParentPlanet != null && systemParentPlanet.BattleStatus_ShotsInstaHitUnlessPlayer ) { debugStage = 2100; if ( systemParent.GetFactionTypeSafe() != FactionType.Player ) { debugStage = 2200; if ( target == null || target.GetFactionTypeSafe() != FactionType.Player ) { //we're firing at something OTHER than a player ship, and we're not a player ship, so do the thing //It IS Relevant, So Do The Insta-Hit doInstaHit = true; } } } #endregion debugStage = 3100; if ( doInstaHit ) { debugStage = 3200; if ( !this.CheckForShotAOEDetonation( System, target, System, Context ) ) { debugStage = 3300; this.DoShotHitLogic( System, System, target, Context ); } return; } debugStage = 4100; if ( typeData.ShotTypeData != null && typeData.ShotTypeData.Category == GameEntityCategory.Ship ) { debugStage = 4200; //hey, we shoot ships, not shots out of this thing! GameEntity_Squad newSquadStyleShot = SpawnSquadStyleShot_ReturnNullIfMPClient( System, originPoint, Context ); debugStage = 4300; if ( newSquadStyleShot == null ) return; //if we are a client debugStage = 4400; MercenaryUnitData parentMercData = systemParent.GetMercenaryUnitDataExt( ExternalDataRetrieval.ReturnNullIfNotFound ); if ( parentMercData != null ) { MercenaryUnitData spawnedShipData = newSquadStyleShot.GetMercenaryUnitDataExt( ExternalDataRetrieval.CreateIfNotFound ); spawnedShipData.MercGroup = parentMercData.MercGroup; newSquadStyleShot.SetMercenaryUnitDataExt( spawnedShipData ); } debugStage = 4500; #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "spawned actual squad style shot:" ).Add( newSquadStyleShot.PrimaryKeyID ).Add( ":" ).Add( newSquadStyleShot.TypeData.InternalName ); #endregion //put us in FRD mode newSquadStyleShot.Orders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, -1 ); debugStage = 4600; //give a direct attack order, but only IF it's actually something the spawned ship can shoot bool foundValidSystem = false; for ( int i = 0; i < newSquadStyleShot.Systems.Count; i++ ) { EntitySystem system = newSquadStyleShot.Systems[i]; if ( typeData.Category != EntitySystemCategory.Weapon ) continue; if ( !system.GetIsTargetValid( target ) ) continue; foundValidSystem = true; break; } debugStage = 4700; if ( foundValidSystem ) { debugStage = 4800; EntityOrder entityOrder = EntityOrder.Create_Attack( newSquadStyleShot, target.PrimaryKeyID, true, OrderSource.HumanPlayer ); //pretend it's the human player so it has more weight newSquadStyleShot.Orders.QueueOrder( newSquadStyleShot, entityOrder ); debugStage = 4900; for ( int i = 0; i < newSquadStyleShot.Systems.Count; i++ ) { EntitySystem system = newSquadStyleShot.Systems[i]; if ( typeData.Category != EntitySystemCategory.Weapon ) continue; //set the new FRD target to be the target system.CurrentFRDTarget = LazyLoadSquadWrapper.Create( target ); system.TimeFRDTargetExpires = 10; //stay for 10 seconds at least (as long as the target lives) system.CurrentFRDPriority = 10000; //consider this super high priority } } debugStage = 5100; //ArcenDebugging.ArcenDebugLog( "Fire shot: " + newSquadStyleShot.TypeData.DisplayName + " at " + target.TypeData.DisplayName, Verbosity.DoNotShow ); //Note! Apparently RemainingDelayUntilEntersSim breaks ships when it's used on them. //if ( typeData.FiresSalvoSequentially ) //{ // runningDelay += FInt.FromParts( 0, Context.RandomToUse.Next( withinSalvoLowEnd, withinSalvoHighEnd ) ); // newSquadStyleShot.RemainingDelayUntilEntersSim = runningDelay; //} //else // newSquadStyleShot.RemainingDelayUntilEntersSim = FInt.Zero; if ( !hasPlayedSoundYet ) { debugStage = 5200; hasPlayedSoundYet = true; //otherwise we spam the queue and I'm just going to discard them anyway! //Chris says: we used to just directly call PlayJustFiredSoundIfIAmShotOutOfASystem(). //But now we set Network_PlayFiringSoundDuringFirstSimLoop, which will make sure it also happens on the client. newSquadStyleShot.Network_PlayFiringSoundDuringFirstSimLoop = true; //Chris says: it was possible that we would wind up processing the shot on the same frame, previously. //That might have been glitchy even in single player, but in MP it definitely would have caused missing animations and sounds on clients. //This is the safer way to be sure ships-as-shots don't act unpredictably. newSquadStyleShot.Network_FrameToStartAnyProcessing = World_AIW2.Instance.Network_CurrentFrameNumber + 1; } } else { debugStage = 8100; GameEntity_Shot newShot = SpawnShot_ReturnNullIfMPClient( System, originPoint, Context ); debugStage = 8200; if ( newShot == null ) return; //if we are a client debugStage = 8300; #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "spawned actual shot:" ).Add( newShot.PrimaryKeyID ).Add( ":" ).Add( newShot.TypeData.InternalName ).Add( " from system index:" ).Add( newShot.OriginSystemIndex ); #endregion newShot.SetTarget( target ); debugStage = 8400; if ( OverridingDamage > 0 ) newShot.OverridingDamageToDo = OverridingDamage; newShot.IsReturnFireShot = IsReturnFireShot; //ArcenDebugging.ArcenDebugLog( "Fire shot: " + systemParent.TypeData.DisplayName + " at " + target.TypeData.DisplayName, Verbosity.DoNotShow ); debugStage = 8500; if ( typeData.FiresSalvoSequentially ) { runningDelay += FInt.FromParts( 0, Context.RandomToUse.Next( withinSalvoLowEnd, withinSalvoHighEnd ) ); newShot.RemainingDelayUntilEntersSim = runningDelay; } else newShot.RemainingDelayUntilEntersSim = FInt.Zero; debugStage = 8600; if ( !hasPlayedSoundYet ) { debugStage = 8700; hasPlayedSoundYet = true; //otherwise we spam the queue and I'm just going to discard them anyway! //Chris says: we used to just directly call PlayJustFiredSound(). //But now we set Network_PlayFiringSoundDuringFirstSimLoop, which will make sure it also happens on the client. newShot.Network_PlayFiringSoundDuringFirstSimLoop = true; //Chris says: it was possible that we would wind up processing the shot on the same frame, previously. //That might have been glitchy even in single player, but in MP it definitely would have caused missing animations and sounds on clients. //This is the safer way to be sure shots don't act unpredictably. newShot.Network_FrameToStartAnyProcessing = World_AIW2.Instance.Network_CurrentFrameNumber + 1; } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "InternalCreateActualShotForSalvo error at debugStage " + debugStage + ", error: " + e, Verbosity.ShowAsError ); } } #endregion public override bool DoShotHitLogic( IShotHitSource ShotHitNeverNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, ArcenSimContext Context ) { FInt dummy; return DoShotHitLogic( ShotHitNeverNull, OriginSystemForShot, Target, false, FInt.OneHundred, out dummy, Context ); } public override bool DoShotHitLogic( IShotHitSource ShotHitNeverNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, ArcenSimContext Context ) { FInt dummy; return DoShotHitLogic( ShotHitNeverNull, OriginSystemForShot, Target, HonorFiniteHitCountAOE, FInt.OneHundred, out dummy, Context ); } public override bool DoShotHitLogic( IShotHitSource ShotHitNeverNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, FInt PercentOfTotalAttackPowerForThisHit, out FInt PercentOfTotalAttackPowerUsedForThisHit, ArcenSimContext Context ) { return DoShotHitLogic( ShotHitNeverNull, OriginSystemForShot, Target, false, PercentOfTotalAttackPowerForThisHit, out PercentOfTotalAttackPowerUsedForThisHit, Context ); } public override bool DoShotHitLogic( IShotHitSource ShotHitNeverNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, FInt PercentOfTotalAttackPowerForThisHit, ArcenSimContext Context ) { FInt dummy; return DoShotHitLogic( ShotHitNeverNull, OriginSystemForShot, Target, HonorFiniteHitCountAOE, PercentOfTotalAttackPowerForThisHit, out dummy, Context ); } public override bool DoShotHitLogic( IShotHitSource ShotHitNeverNull, EntitySystem OriginSystemForShotOrNull, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, FInt PercentOfTotalAttackPowerForThisHit, out FInt PercentOfTotalAttackPowerUsedForThisHit, ArcenSimContext Context ) { int debugStage = 0; try { PercentOfTotalAttackPowerUsedForThisHit = FInt.Zero; debugStage = 100; if ( OriginSystemForShotOrNull != null && !OriginSystemForShotOrNull.GetCanHitByDesireOrNot_AndIAlreadyKnowIAmAWeapon( Target ) ) return false; if ( Target == null ) return false; if ( ShotHitNeverNull == null ) { ArcenDebugging.ArcenDebugLog( "DoShotHitLogic: Null ShotHitNeverNull!", Verbosity.ShowAsError ); return false; } PlanetFaction shotFaction = ShotHitNeverNull.GetPlanetFaction(); Planet shotPlanet = ShotHitNeverNull.GetPlanet(); StateOfMatterTypeData shotStateOfMatter = ShotHitNeverNull.GetCurrentStateOfMatter(); if ( Target.CurrentStateOfMatter != shotStateOfMatter ) { PercentOfTotalAttackPowerUsedForThisHit = FInt.Zero; return false; } debugStage = 200; bool wasAlive = !Target.GetHasBeenDestroyed(); //ArcenDebugging.ArcenDebugLog( OriginSystemForShot.ParentEntity.TypeData.DisplayName + " against " + Target.TypeData.DisplayName + " initial check", Verbosity.DoNotShow ); debugStage = 300; GameEntity_Squad protectingShieldThatTookTheHit = null; bool foundProtectorButCouldNotHitDueToFiniteHitCountAOE = false; if ( OriginSystemForShotOrNull != null ) { debugStage = 400; OriginSystemForShotOrNull.LastGameSecondMyShotHit = World_AIW2.Instance.GameSecond; OriginSystemForShotOrNull.LastTotalDamageMyShotDidCaused = 0; OriginSystemForShotOrNull.LastDamageAbortCode = 0; } { #region tracing bool trace = Engine_AIW2.TraceAtAll; if ( trace ) { if ( Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && OriginSystemForShotOrNull != null && OriginSystemForShotOrNull.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 && OriginSystemForShotOrNull != null ) tracingBuffer.Add( "Tracing DoHitLogic for shot from " ).Add( OriginSystemForShotOrNull.TypeData.InternalName_Longer ).Add( " from " ).Add( OriginSystemForShotOrNull.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( OriginSystemForShotOrNull.ParentEntity.PrimaryKeyID ); #endregion debugStage = 500; protectingShieldThatTookTheHit = this.FindProtectingForcefieldToHitInsteadOfTarget( ShotHitNeverNull, OriginSystemForShotOrNull, Target, HonorFiniteHitCountAOE, out foundProtectorButCouldNotHitDueToFiniteHitCountAOE, Context ); //ArcenDebugging.ArcenDebugLog( OriginSystemForShot.ParentEntity.TypeData.DisplayName + " " + OriginSystemForShot.TypeData.FiresThroughEnemyShields + " " + // (protectingShieldThatTookTheHit == null ? "null" : protectingShieldThatTookTheHit.TypeData.DisplayName ), Verbosity.DoNotShow ); debugStage = 600; int actualDamageDone = 0, damageAbortCode = 0, attackPowerAgainstThisTarget = 0, adjustedAttackPower = 0; if ( protectingShieldThatTookTheHit != null ) //if there's a forcefield protecting us and we can hit it { if ( OriginSystemForShotOrNull != null ) { debugStage = 700; attackPowerAgainstThisTarget = OriginSystemForShotOrNull.GetAttackPowerAgainst( protectingShieldThatTookTheHit, tracingBuffer, true, ShotHitNeverNull.GetOverridingDamageToDo() ); adjustedAttackPower = ((attackPowerAgainstThisTarget * PercentOfTotalAttackPowerForThisHit) / 100).IntValue; #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "int adjustedAttackPower = ( ( attackPowerAgainstThisTarget " + attackPowerAgainstThisTarget + " * PercentOfTotalAttackPowerForThisHit " + PercentOfTotalAttackPowerForThisHit + " ) / 100 ).IntValue = " ).Add( adjustedAttackPower ); #endregion protectingShieldThatTookTheHit.TakeDamageDirectly( adjustedAttackPower, OriginSystemForShotOrNull, ShotHitNeverNull, DamageSource.SomeSortOfEnemy, false, HonorFiniteHitCountAOE, OriginSystemForShotOrNull.TypeData.MaxStacksToKill, true, out actualDamageDone, out damageAbortCode, Context, trace ? tracingBuffer : null ); #region ReturnsThisPercentageOfDamageIfDamagedByEnemyAttack if ( protectingShieldThatTookTheHit.TypeData.ReturnsThisPercentageOfDamageIfDamagedByEnemyAttack > FInt.Zero && actualDamageDone > 0 ) { GameEntity_Squad firingShip = OriginSystemForShotOrNull?.ParentEntity; if ( firingShip != null ) { int damageAmount = (protectingShieldThatTookTheHit.TypeData.ReturnsThisPercentageOfDamageIfDamagedByEnemyAttack * actualDamageDone).GetNearestIntPreferringHigher(); int electrotoxicDamageDone; int electrotoxicDamageAbortCode; firingShip.TakeDamageDirectly( damageAmount, null, null, DamageSource.SomeSortOfEnemy, false, false, -1, false, out electrotoxicDamageDone, out electrotoxicDamageAbortCode, Context ); } } #endregion #region HasAWeaponThatReturnsFireWhenParentHit if ( protectingShieldThatTookTheHit.DataForMark != null && protectingShieldThatTookTheHit.DataForMark.HasAWeaponThatReturnsFireWhenParentHit && !ShotHitNeverNull.GetIsReturnFireShot() ) //no chains of return-fire shots! { GameEntity_Squad firingShip = OriginSystemForShotOrNull?.ParentEntity; if ( firingShip != null ) { bool hasPlayedSoundYet = false; FInt runningDelay = FInt.Zero; foreach ( var system in protectingShieldThatTookTheHit.Systems ) { if ( system == null ) continue; EntitySystemTypeData typeData = system.TypeData; if ( typeData == null ) continue; if ( typeData.FiringTiming != FiringTiming.WhenParentEntityHit ) continue; //if the weapon is not the "when parent entity hit" type if ( system.ComputeDisabledReason() != ArcenRejectionReason.Unknown ) continue; //if the weapon is off if ( typeData.ReturnsThisPercentageOfDamageWhenFiringRetaliatoryShot > FInt.Zero ) { if ( actualDamageDone > 0 ) { int damageAmount = (typeData.ReturnsThisPercentageOfDamageWhenFiringRetaliatoryShot * actualDamageDone).GetNearestIntPreferringHigher(); if ( damageAmount > 0 ) { bool doShotsAllInstaHit = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "ShotsAllInstaHit" ); InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, firingShip, Target.WorldLocation, Context, system, trace, tracingBuffer, 0, 0, damageAmount, true, doShotsAllInstaHit ); } } } else { bool doShotsAllInstaHit = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "ShotsAllInstaHit" ); InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, firingShip, Target.WorldLocation, Context, system, trace, tracingBuffer, 0, 0, 0, true, doShotsAllInstaHit ); } } } } #endregion } debugStage = 800; //The "time last in combat" value is used to check when we should play //audio cues. We will eventually deprecate CheckForDamageTakenAudioCue and handle //that in the External faction DoPerSecond code based on the TimeFactionLastInCombatOnPlanet //values if ( !Target.suppressDeathAudioCue && !protectingShieldThatTookTheHit.suppressDeathAudioCue ) protectingShieldThatTookTheHit.PlanetFaction.TimeFactionLastInCombatOnPlanet = World_AIW2.Instance.GameSecond; debugStage = 900; protectingShieldThatTookTheHit.CheckForDamageTakenAudioCue( true, Target.TypeData.IsKingUnit ); debugStage = 1000; protectingShieldThatTookTheHit.LastTimeTakenDamageFromBeingShotByAnyone = World_AIW2.Instance.GameSecond; Target.LastTimeTakenDamageFromBeingShotByAnyone = World_AIW2.Instance.GameSecond; //aggro target even though it was protected! } else if ( foundProtectorButCouldNotHitDueToFiniteHitCountAOE ) //if there's a forcefield protecting us and we cannot hit it because we already did { //do... nothing I guess? This is what we always used to do } else //no forcefield protecting me, so we just shoot the thing { debugStage = 2000; if ( OriginSystemForShotOrNull != null ) { debugStage = 2100; attackPowerAgainstThisTarget = OriginSystemForShotOrNull.GetAttackPowerAgainst( Target, tracingBuffer, true, ShotHitNeverNull.GetOverridingDamageToDo() ); adjustedAttackPower = ((attackPowerAgainstThisTarget * PercentOfTotalAttackPowerForThisHit) / 100).IntValue; #region tracing if ( trace ) tracingBuffer.Add( "\n" ).Add( "int adjustedAttackPower = ( ( attackPowerAgainstThisTarget " + attackPowerAgainstThisTarget + " * PercentOfTotalAttackPowerForThisHit " + PercentOfTotalAttackPowerForThisHit + " ) / 100 ).IntValue = " ).Add( adjustedAttackPower ); #endregion //the new logic handles this directly in FindProtectingForcefieldToHitInsteadOfTarget //if ( protectingShieldThatTookTheHit == null && Target.TypeData.ProjectsForcefield && Target.GetCurrentShieldPoints() > 0 ) // protectingShieldThatTookTheHit = Target; //make this visually hit the outside of the shield if someone targets a forcefield directly! try { debugStage = 2200; Target.TakeDamageDirectly( adjustedAttackPower, OriginSystemForShotOrNull, ShotHitNeverNull, DamageSource.SomeSortOfEnemy, false, HonorFiniteHitCountAOE, OriginSystemForShotOrNull.TypeData.MaxStacksToKill, false, out actualDamageDone, out damageAbortCode, Context, trace ? tracingBuffer : null ); debugStage = 2210; #region ReturnsThisPercentageOfDamageIfDamagedByEnemyAttack if ( Target.TypeData.ReturnsThisPercentageOfDamageIfDamagedByEnemyAttack > FInt.Zero && actualDamageDone > 0 ) { debugStage = 2220; GameEntity_Squad firingShip = OriginSystemForShotOrNull?.ParentEntity; if ( firingShip != null ) { int damageAmount = (Target.TypeData.ReturnsThisPercentageOfDamageIfDamagedByEnemyAttack * actualDamageDone).GetNearestIntPreferringHigher(); int electrotoxicDamageDone; int electrotoxicDamageAbortCode; firingShip.TakeDamageDirectly( damageAmount, null, null, DamageSource.SomeSortOfEnemy, false, false, -1, false, out electrotoxicDamageDone, out electrotoxicDamageAbortCode, Context ); } } #endregion debugStage = 2230; #region HasAWeaponThatReturnsFireWhenParentHit debugStage = 2240; if ( Target.DataForMark != null && Target.DataForMark.HasAWeaponThatReturnsFireWhenParentHit && !ShotHitNeverNull.GetIsReturnFireShot() ) //no chains of return-fire shots! { debugStage = 2250; GameEntity_Squad firingShip = OriginSystemForShotOrNull?.ParentEntity; if ( firingShip != null ) { bool hasPlayedSoundYet = false; FInt runningDelay = FInt.Zero; foreach ( var system in Target.Systems ) { if ( system == null ) continue; EntitySystemTypeData typeData = system.TypeData; if ( typeData == null ) continue; if ( typeData.FiringTiming != FiringTiming.WhenParentEntityHit ) continue; //if the weapon is not the "when parent entity hit" type if ( system.ComputeDisabledReason() != ArcenRejectionReason.Unknown ) continue; //if the weapon is off if ( typeData.ReturnsThisPercentageOfDamageWhenFiringRetaliatoryShot > FInt.Zero ) { if ( actualDamageDone > 0 ) { int damageAmount = (typeData.ReturnsThisPercentageOfDamageWhenFiringRetaliatoryShot * actualDamageDone).GetNearestIntPreferringHigher(); if ( damageAmount > 0 ) { bool doShotsAllInstaHit = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "ShotsAllInstaHit" ); InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, firingShip, Target.WorldLocation, Context, system, trace, tracingBuffer, 0, 0, damageAmount, true, doShotsAllInstaHit ); } } } else { bool doShotsAllInstaHit = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "ShotsAllInstaHit" ); InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, firingShip, Target.WorldLocation, Context, system, trace, tracingBuffer, 0, 0, 0, true, doShotsAllInstaHit ); } } } } #endregion } catch (ThreadAbortException ) { return true; } //this is okay! Just thread shutting down. catch ( Exception e ) { if ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) { //don't worry about this on a client } else throw e; } } //ArcenDebugging.ArcenDebugLog( OriginSystemForShot.ParentEntity.TypeData.DisplayName + " against " + Target.TypeData.DisplayName + " " + // actualDamageDone + " dmg", Verbosity.DoNotShow ); debugStage = 2400; //The "time last in combat" value is used to check when we should play //audio cues if ( !Target.suppressDeathAudioCue ) Target.PlanetFaction.TimeFactionLastInCombatOnPlanet = World_AIW2.Instance.GameSecond; debugStage = 2500; Target.CheckForDamageTakenAudioCue( false, false ); debugStage = 2600; Target.LastTimeTakenDamageFromBeingShotByAnyone = World_AIW2.Instance.GameSecond; } //ArcenDebugging.ArcenDebugLog( OriginSystemForShot.ParentEntity.TypeData.DisplayName + " " + OriginSystemForShot.TypeData.FiresThroughEnemyShields + " " + // (protectingShieldThatTookTheHit == null ? "nullShield" : protectingShieldThatTookTheHit.TypeData.DisplayName), Verbosity.DoNotShow ); debugStage = 4000; if ( OriginSystemForShotOrNull != null ) { debugStage = 4100; OriginSystemForShotOrNull.LastTotalDamageMyShotDidCaused += actualDamageDone; if ( damageAbortCode != 0 ) OriginSystemForShotOrNull.LastDamageAbortCode = damageAbortCode; } debugStage = 5000; #region tracing if ( trace ) { tracingBuffer.Add( "\n" ).Add( "actualDamageDone = " ).Add( actualDamageDone ); ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow ); } #endregion if ( actualDamageDone <= 0 ) return false; debugStage = 5100; if ( attackPowerAgainstThisTarget > 0 ) // it will be, but just to be sure { debugStage = 5200; if ( OriginSystemForShotOrNull != null ) { if ( OriginSystemForShotOrNull.TypeData.DamageAmplification ) { debugStage = 5300; //no more than 15 seconds, no less than 1 Target.IncomingDamageAmplifiedDuration_Max15 = (byte)Math.Max( 1, Math.Min( 15, (int)OriginSystemForShotOrNull.TypeData.DamageAmplificationDuration_Max15 ) ); debugStage = 5400; if ( OriginSystemForShotOrNull.DataForMark.DamageAmplificationFlat > 0 ) Target.IncomingDamageAmplifiedByFlat = OriginSystemForShotOrNull.DataForMark.DamageAmplificationFlat; debugStage = 5500; if ( OriginSystemForShotOrNull.TypeData.DamageAmplificationMult != FInt.One ) Target.IncomingDamageAmplifiedByMult = OriginSystemForShotOrNull.TypeData.DamageAmplificationMult; } } debugStage = 5600; 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); } debugStage = 6000; if ( OriginSystemForShotOrNull != null && OriginSystemForShotOrNull.ParentEntity.TypeData.HealthChangePerDamageDealt != FInt.Zero && actualDamageDone > 0 ) { debugStage = 6100; if ( OriginSystemForShotOrNull.ParentEntity.TypeData.HealthChangePerDamageDealt < FInt.Zero ) { debugStage = 6200; int selfDamage = (actualDamageDone * -OriginSystemForShotOrNull.ParentEntity.TypeData.HealthChangePerDamageDealt).IntValue; debugStage = 6300; OriginSystemForShotOrNull.ParentEntity.TakeDamageDirectly( selfDamage, null, null, DamageSource.SelfDamageFromMyOwnWeapons, Context ); debugStage = 6400; if ( OriginSystemForShotOrNull.ParentEntity.TypeData.CustomRepairImpossibleForSecondsAfterDamagedByEnemy_Max15 > 0 ) OriginSystemForShotOrNull.ParentEntity.RepairImpossibleForSeconds = (byte)OriginSystemForShotOrNull.ParentEntity.TypeData.CustomRepairImpossibleForSecondsAfterDamagedByEnemy_Max15; else OriginSystemForShotOrNull.ParentEntity.RepairImpossibleForSeconds = (byte)ExternalConstants.Instance.Balance_RepairImpossibleForSecondsAfterDamagedByEnemy; } else { debugStage = 7000; int selfHealing = (actualDamageDone * OriginSystemForShotOrNull.ParentEntity.TypeData.HealthChangePerDamageDealt).IntValue; debugStage = 7100; selfHealing -= OriginSystemForShotOrNull.ParentEntity.TakeHullRepair( selfHealing ); debugStage = 7200; if ( selfHealing > 0 ) { debugStage = 7300; OriginSystemForShotOrNull.ParentEntity.TakeShieldRepair( selfHealing ); } } } } debugStage = 9000; World_AIW2.Instance.TotalShotsHit++; debugStage = 9100; bool wholeSquadKilled = wasAlive && Target.GetHasBeenDestroyed(); int shipsKilled = wholeSquadKilled ? 1 : 0; debugStage = 9200; GameEntity_Shot shotOrNull = null; if ( ShotHitNeverNull is GameEntity_Shot ) shotOrNull = ShotHitNeverNull as GameEntity_Shot; if ( shotPlanet != null && shotPlanet.BattleStatus == PlanetBattleStatus.Tier1_PlayerLookingAtMe && shotOrNull != null ) { debugStage = 9300; if ( shotOrNull.VisualLinkObject_GenericOnly != null || shotOrNull.InstancedRenderer != null ) { debugStage = 9400; if ( shotOrNull.InstancedRenderer != null ) { debugStage = 9500; shotOrNull.InstancedRenderer.ReactToShotHittingSquad( Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled ); } else { debugStage = 9600; Engine_AIW2.Instance.PresentationLayer.ReactToShotHittingSquad( shotOrNull, Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled ); } } else { debugStage = 9700; if ( Target.InstancedRenderer != null ) { debugStage = 9800; Target.InstancedRenderer.ReactToShotHittingSquad( Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled ); } } } else { debugStage = 11000; if ( (shipsKilled > 0 || wholeSquadKilled) && Target.TypeData.Category == GameEntityCategory.Ship ) { debugStage = 11100; //ArcenDebugging.ArcenDebugLog( "Killed ship! " + Target.TypeData.InternalName + " InstancedRenderer = " + (Target.InstancedRenderer == null ? "null" : "ok"), Verbosity.DoNotShow ); Target.PlayJustDiedSoundIfNotOnCurrentLocalPlanet( Context, wholeSquadKilled ); } } return true; } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "DoShotHitLogic error at debugStage " + debugStage + ": " + e, Verbosity.ShowAsError ); PercentOfTotalAttackPowerUsedForThisHit = FInt.Zero; return false; } } #region FindProtectingForcefieldToHitInsteadOfTarget private GameEntity_Squad FindProtectingForcefieldToHitInsteadOfTarget( IShotHitSource ShotHitNeverNull, EntitySystem OriginSystemForShotOrNull, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, out bool FoundProtectorButCouldNotHitDueToFiniteHitCountAOE, ArcenSimContext Context ) { FoundProtectorButCouldNotHitDueToFiniteHitCountAOE = false; if ( OriginSystemForShotOrNull != null && OriginSystemForShotOrNull.TypeData.FiresThroughEnemyShields ) { //ArcenDebugging.ArcenDebugLog( OriginSystemForShot.ParentEntity.TypeData.DisplayName + " FiresThroughEnemyShields", Verbosity.DoNotShow ); return null; //don't even check for forcefields if we shoot past them! } if ( Target == null ) { //ArcenDebugging.ArcenDebugLog( Target.GetIsProtectedByAnyForcefield() + " GetIsProtectedByAnyForcefield or null", Verbosity.DoNotShow ); return null; //if there's no target or it's not protected by any forcefields, then skip it for sure also. } if ( !Target.GetIsProtectedByAnyForcefield() ) { if ( Target.TypeData.ProjectsForcefield && Target.GetCurrentShieldPoints() > 0 ) return Target; //bring back the target itself as the protecting shield if it is a shield generator not under other generators. // the idea with bringing it back on its own is that then it can take only shield damage, not hull damage, which seems more correct. // that also makes it consistent whether you attack the generator or something under it, which is definitely more correct. return null; } int debugStage = 1; try { debugStage = 100; for ( int shieldType = 0; shieldType < 2; shieldType++ ) { debugStage = 200; List protectingShields = shieldType == 0 ? Target.ProtectingShields_ReduceDamage : Target.ProtectingShields_NoDamageReduction; if ( protectingShields.Count == 0 ) { //ArcenDebugging.ArcenDebugLog( "protectingShields.Count == 0 at shieldType " + shieldType, Verbosity.DoNotShow ); continue; } //ArcenDebugging.ArcenDebugLog( "protectingShields count to check: " + protectingShields.Count, Verbosity.DoNotShow ); debugStage = 400; for ( int i = protectingShields.Count - 1; i >= 0; i-- ) { GameEntity_Squad protector = null; try { protector = protectingShields[i].GetSquad(); } catch { continue; } //threading issue debugStage = 500; if ( protector == null || protector.HasBeenRemovedFromSim || protector.ToBeRemovedAtEndOfThisFrame || protector.GetCurrentShieldPoints() <= 0 ) { //ArcenDebugging.ArcenDebugLog( "protectingShields to be removed at index " + i, Verbosity.DoNotShow ); try { protectingShields.RemoveAt( i ); } catch { } continue; } debugStage = 400; if ( protector.Debug_IgnoresDamage ) { //ArcenDebugging.ArcenDebugLog( "protectingShields Debug_IgnoresDamage " + i, Verbosity.DoNotShow ); continue; } debugStage = 500; if ( HonorFiniteHitCountAOE && Context.WorkingAOETargetsThatHaveBeenHitList.Contains( protector ) ) { //ArcenDebugging.ArcenDebugLog( "protectingShields FoundProtectorButCouldNotHitDueToFiniteHitCountAOE " + i, Verbosity.DoNotShow ); FoundProtectorButCouldNotHitDueToFiniteHitCountAOE = true; continue; } debugStage = 600; if ( HonorFiniteHitCountAOE ) Context.WorkingAOETargetsThatHaveBeenHitList.Add( protector ); FoundProtectorButCouldNotHitDueToFiniteHitCountAOE = false; //we're all good now //ArcenDebugging.ArcenDebugLog( "protectingShields yay return protector " + i, Verbosity.DoNotShow ); return protector; // even if the shield didn't have enough points to block the shot, it doesn't proceed further } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "FindProtectingForcefieldToHitInsteadOfTarget error at debugStage " + debugStage + ": " + e, Verbosity.ShowAsError ); } //ArcenDebugging.ArcenDebugLog( "protectingShields found nothing?", Verbosity.DoNotShow ); return null; } #endregion [ThreadStatic] private static List workingEmissionPoints = null; [ThreadStatic] private static List entitiesThatWeShouldChainFromThisCycle = null; [ThreadStatic] private static List allValidTargets = null; public override bool CheckForShotAOEDetonation( IShotHitSource ShotHitNeverNull, GameEntity_Squad TargetOrNull, EntitySystem OriginSystemForShotOrNull, ArcenSimContext Context ) { int debugStage = 0; try { if ( ShotHitNeverNull == null ) { ArcenDebugging.ArcenDebugLog( "CheckForShotAOEDetonation: Null ShotHitNeverNull!", Verbosity.ShowAsError ); return false; } debugStage = 10; int aoe = (OriginSystemForShotOrNull != null ? OriginSystemForShotOrNull.DataForMark.ShotAreaOfEffect : ShotHitNeverNull.GetShotAreaOfEffect() ); debugStage = 20; GameEntity_Squad originEntity = OriginSystemForShotOrNull == null ? null : OriginSystemForShotOrNull.ParentEntity; debugStage = 30; PlanetFaction myFaction = ShotHitNeverNull.GetPlanetFaction(); if ( myFaction == null ) myFaction = originEntity.PlanetFaction; debugStage = 31; Planet myPlanet = ShotHitNeverNull.GetPlanet(); if ( myPlanet == null ) myPlanet = originEntity.Planet; debugStage = 32; StateOfMatterTypeData stateofMatter = ShotHitNeverNull.GetCurrentStateOfMatter(); if ( stateofMatter == null ) stateofMatter = (originEntity != null ? originEntity.CurrentStateOfMatter : StateOfMatterTypeDataTable.Instance.DefaultRow); debugStage = 33; if ( !stateofMatter.CanTargetOtherUnitsInThisState ) return true; //didn't work, but that's because we are in a state where we can't do that if ( aoe > 0 ) { debugStage = 1000; debugStage = 1100; if ( myFaction == null ) return true; //didn't work, but still an AOE shot so report true debugStage = 1200; PlanetFaction fac; debugStage = 1300; ArcenPoint myLoc = ShotHitNeverNull.GetWorldLocation(); debugStage = 1400; Context.WorkingAOETargetsToHitList.Clear(); int distance; debugStage = 1600; for ( int i = 0; i < myPlanet.Factions.Count; i++ ) { fac = myPlanet.Factions[i]; if ( fac == null ) continue; debugStage = 1700; //doing this only once per faction, and more centrally, is quite efficient when there's a lot of entities if ( (OriginSystemForShotOrNull == null || !OriginSystemForShotOrNull.TypeData.AOEHitsFriendlyTargets ) && !myFaction.GetIsHostileTowards( fac ) ) continue; debugStage = 1800; fac.Entities.DoForEntities( delegate ( GameEntity_Squad otherEntity ) { if ( otherEntity == null ) return DelReturn.Continue; if ( otherEntity.CurrentStateOfMatter != stateofMatter ) return DelReturn.Continue; try { distance = aoe + otherEntity.DataForMark.Radius + otherEntity.CalculatedCurrentShieldRadius; //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 ); } catch ( Exception e) { if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLogSingleLine( "Exception in AOE calculation 1800: " + e, Verbosity.ShowAsError ); } return DelReturn.Continue; } ); } debugStage = 2200; int numberOfTargetsToHit = Context.WorkingAOETargetsToHitList.Count; debugStage = 2400; int maxTargets = (OriginSystemForShotOrNull == null ? 10 : OriginSystemForShotOrNull.TypeData.MaximumNumberOfTargetsHitPerShot ); if ( maxTargets > 0 ) numberOfTargetsToHit = maxTargets; if ( numberOfTargetsToHit <= Context.WorkingAOETargetsToHitList.Count ) ArcenArrays.Randomize( Context.WorkingAOETargetsToHitList, Context.RandomToUse, 3 ); debugStage = 2500; if ( OriginSystemForShotOrNull != null && OriginSystemForShotOrNull.TypeData.AOESpreadsDamageAmongAvailableTargets ) { debugStage = 2600; if ( Context.WorkingAOETargetsToHitList.Count > 0 ) { int totalOutput = OriginSystemForShotOrNull.DataForMark.CalculateShipDamagePerShot( originEntity ); int remainingOutput = totalOutput; int maxLoopCount = 100; bool checkAgain = true; while ( remainingOutput > 0 && checkAgain && maxLoopCount-- > 0 ) { checkAgain = false; FInt portionPerEachOutOf100 = Mat.Max( (FInt)10, FInt.OneHundred / 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 debugStage = 2700; 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; } debugStage = 2800; // 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( ShotHitNeverNull, OriginSystemForShotOrNull, 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 ( OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget > FInt.Zero ) { damageDone = ( (OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget * damageDone) / 100 ).GetNearestIntPreferringHigher(); } if ( damageDone <= 0 ) { Context.WorkingAOETargetsToHitList.RemoveAt( i-- ); continue; } remainingOutput -= damageDone; checkAgain = true; } } } debugStage = 2900; } else { debugStage = 4000; Context.WorkingAOETargetsThatHaveBeenHitList.Clear(); for ( int i = 0; i < Context.WorkingAOETargetsToHitList.Count; i++ ) { if ( numberOfTargetsToHit <= 0 ) break; debugStage = 4100; GameEntity_Squad targetThisTime = Context.WorkingAOETargetsToHitList[i]; if ( targetThisTime == null ) continue; debugStage = 4120; FInt percentDamageToDo = FInt.OneHundred; if ( TargetOrNull != targetThisTime && OriginSystemForShotOrNull != null && OriginSystemForShotOrNull.TypeData != null ) { debugStage = 4130; if ( OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget > FInt.Zero ) { debugStage = 4140; percentDamageToDo = OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget; } } debugStage = 4150; if ( EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, targetThisTime, true, percentDamageToDo, Context ) ) numberOfTargetsToHit--; } debugStage = 4160; Context.WorkingAOETargetsThatHaveBeenHitList.Clear(); } ShotHitNeverNull.IncrementPostAOEData(); return true; } else if ( originEntity != null && OriginSystemForShotOrNull != null && OriginSystemForShotOrNull.TypeData.BeamLengthMultiplier > FInt.Zero ) { debugStage = 7000; 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 ) && OriginSystemForShotOrNull.ParentEntity == GameEntity_Base.CurrentlyHoveredOver; ArcenCharacterBuffer tracingBuffer = null; if ( trace ) tracingBuffer = new ArcenCharacterBuffer(); if ( trace ) tracingBuffer.Add( "Tracing CheckForAOEDetonation for shot from " ).Add( OriginSystemForShotOrNull.TypeData.InternalName_Longer ).Add( " from " ).Add( OriginSystemForShotOrNull.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( OriginSystemForShotOrNull.ParentEntity.PrimaryKeyID ); #endregion debugStage = 7100; if ( OriginSystemForShotOrNull.TypeData.HitsAllIntersectingTargets ) { bool drawVisuals = GameSettings_AIW2.Current.GetBool( ArcenBoolSetting_AIW2.DrawBeamWeaponVisuals ) && myPlanet != null && myPlanet.Index == PlayerAccount_AIW2.GetViewingPlanetIndexSafe(); if ( OriginSystemForShotOrNull.TypeData.NumberBeamsToFire > 1 && OriginSystemForShotOrNull.TypeData.DistanceFromCenterForBeamEmission > 0 ) { if ( workingEmissionPoints == null ) workingEmissionPoints = new List(); int beamLength = (OriginSystemForShotOrNull.DataForMark.CalculateActualRange( originEntity ) * OriginSystemForShotOrNull.TypeData.BeamLengthMultiplier).IntValue; int beamCount = OriginSystemForShotOrNull.TypeData.NumberBeamsToFire; Mat.FillRingOfPointsAtDistance( originEntity.WorldLocation, ref workingEmissionPoints, OriginSystemForShotOrNull.TypeData.NumberBeamsToFire, OriginSystemForShotOrNull.TypeData.DistanceFromCenterForBeamEmission ); ArcenPoint endPoint = targetEntity.WorldLocation; int maxTargets = OriginSystemForShotOrNull.TypeData.MaximumNumberOfTargetsHitPerShot != 0 ? OriginSystemForShotOrNull.TypeData.MaximumNumberOfTargetsHitPerShot : -1; foreach ( var originPoint in workingEmissionPoints ) { if ( drawVisuals ) { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.RingBeamCannon]; if ( lineType != null && originEntity != null ) { LineSetWrapper wrapper = originEntity.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = originPoint - originEntity.WorldLocation; lineSet.TargetPoint = endPoint; lineSet.FromEntity = originEntity; } } } List intersectingTargets = OriginSystemForShotOrNull.GetEnemyEntitiesIntersectingInstaFireConicalShot( originPoint, endPoint, beamLength, targetEntity, Context, tracingBuffer ); // Check for hitting our primary. bool primaryTargetHit = false; int coilbeamTargetsToSplit = Math.Max( intersectingTargets.Count - 1, 1 ); FInt percentDamageForCoilbeam = FInt.OneHundred / coilbeamTargetsToSplit; 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]; if ( intersectedTarget.CurrentStateOfMatter != stateofMatter ) continue; FInt damagePercentageToDo = FInt.OneHundred; if ( intersectedTarget.PrimaryKeyID == targetEntity.PrimaryKeyID ) { // Mark hit if its our primary. primaryTargetHit = true; if ( OriginSystemForShotOrNull.TypeData.IsCoilbeam ) { //do coilbeam damage here EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, intersectedTarget, Context ); continue; } } else { if ( OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget > FInt.Zero ) { damagePercentageToDo = OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget; } } FInt unused; if ( OriginSystemForShotOrNull.TypeData.IsCoilbeam ) //this is a non-primary target hit with the coilbeam EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, intersectedTarget, false, percentDamageForCoilbeam * damagePercentageToDo, out unused, Context ); else //generic beam hit; could be primary or secondary EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, intersectedTarget, false, damagePercentageToDo, Context ); } // If we still have targets left, and we haven't yet hit our primary target, hit it. // No need to update our endpoint since this hit only occurs if we have already pierced everything else. if ( !primaryTargetHit && intersectingTargets.Count < maxTargets ) EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, targetEntity, Context ); intersectingTargets.Clear(); } #region tracing if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow ); #endregion return true; } else //beams all come from the center { ArcenPoint originPoint = originEntity.WorldLocation; ArcenPoint targetPoint = targetEntity.WorldLocation; AngleDegrees angle = originPoint.GetAngleToDegrees( targetPoint ); int beamLength = (OriginSystemForShotOrNull.DataForMark.CalculateActualRange( originEntity ) * OriginSystemForShotOrNull.TypeData.BeamLengthMultiplier).IntValue; int beamCount = OriginSystemForShotOrNull.TypeData.NumberBeamsToFire; AngleDegrees beamBeginAngle = beamCount == 1 ? angle : angle.Add( -((OriginSystemForShotOrNull.TypeData.DegreesOffsetPerBeam * beamCount) / 2) ); if ( OriginSystemForShotOrNull.TypeData.SecondsForBeamToRotateFully > 0 ) { //if this is a spinning beam, the origin beam now starts at an additional offset int degreesPerRotation = 360 / OriginSystemForShotOrNull.TypeData.SecondsForBeamToRotateFully; int secondsForRotation = World_AIW2.Instance.GameSecond % OriginSystemForShotOrNull.TypeData.SecondsForBeamToRotateFully; int rotationAmount = secondsForRotation * degreesPerRotation; AngleDegrees rotation = AngleDegrees.Create( rotationAmount ); beamBeginAngle = beamBeginAngle.Add( rotationAmount ); } #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 AngleDegrees workingAngle = beamBeginAngle; int maxTargets = OriginSystemForShotOrNull.TypeData.MaximumNumberOfTargetsHitPerShot != 0 ? OriginSystemForShotOrNull.TypeData.MaximumNumberOfTargetsHitPerShot : -1; for ( int i = 0; i < beamCount; i++, workingAngle += OriginSystemForShotOrNull.TypeData.DegreesOffsetPerBeam ) { //GetPointAtAngleAndDistance is not remotely precise enough for our purposes here! ArcenPoint endPoint = originPoint.GetPointAtAngleAndDistance( workingAngle, beamLength ); List intersectingTargets = OriginSystemForShotOrNull.GetEnemyEntitiesIntersectingInstaFireConicalShot( originPoint, endPoint, beamLength, targetEntity, Context, tracingBuffer ); // Check for hitting our primary. bool primaryTargetHit = false; int coilbeamTargetsToSplit = Math.Max( intersectingTargets.Count - 1, 1 ); FInt percentDamageForCoilbeam = (FInt.OneHundred / coilbeamTargetsToSplit) / 100 ; 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]; if ( intersectedTarget.CurrentStateOfMatter != stateofMatter ) continue; FInt damagePercentageToDo = FInt.OneHundred; if ( intersectedTarget.PrimaryKeyID == targetEntity.PrimaryKeyID ) { // Mark hit if its our primary. primaryTargetHit = true; if ( OriginSystemForShotOrNull.TypeData.IsCoilbeam ) { //do coilbeam damage here EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, intersectedTarget, Context ); continue; } } else { if ( OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget > FInt.Zero ) { damagePercentageToDo = OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget; } } FInt unused; if ( OriginSystemForShotOrNull.TypeData.IsCoilbeam ) //this is a non-primary target hit with the coilbeam EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, intersectedTarget, false, percentDamageForCoilbeam * damagePercentageToDo, out unused, Context ); else //generic beam hit; could be primary or secondary EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, intersectedTarget, false, damagePercentageToDo, Context ); } // If we still have targets left, and we haven't yet hit our primary target, hit it. // No need to update our endpoint since this hit only occurs if we have already pierced everything else. if ( !primaryTargetHit && intersectingTargets.Count < maxTargets ) EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, targetEntity, Context ); intersectingTargets.Clear(); //UnityEngine.Debug.Log( i + " " + beamCount + " " + workingAngle + " " + endPoint + " " + originPoint.GetPointAtAngleAndDistance( workingAngle, beamLength ) ); if ( drawVisuals ) { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.BeamCannon]; if ( lineType != null && originEntity != null ) { LineSetWrapper wrapper = originEntity.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = ArcenPoint.ZeroZeroPoint; lineSet.TargetPoint = endPoint; lineSet.FromEntity = originEntity; } } } } #region tracing if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow ); #endregion return true; } } //chain beam effects else if ( OriginSystemForShotOrNull.TypeData.BeamChainsOutToTargetsXTimes > 0 && OriginSystemForShotOrNull.TypeData.BeamChainsOutToTargetsXRange > 0 ) { int maxTargets = OriginSystemForShotOrNull.TypeData.MaximumNumberOfTargetsHitPerShot; int remainingTargets = maxTargets > 0 ? maxTargets : 9999999; //first clear our working lists that we need for this Context.WorkingAOETargetsThatHaveBeenHitList.Clear(); Context.WorkingAOETargetsToHitList.Clear(); if ( entitiesThatWeShouldChainFromThisCycle == null ) entitiesThatWeShouldChainFromThisCycle = new List(); else entitiesThatWeShouldChainFromThisCycle.Clear(); int maxTargetsPerSource = OriginSystemForShotOrNull.TypeData.BeamChainsOutToMaxTargetsFromEachSource; List entitiesToChainFromNextTime = Context.WorkingAOETargetsToHitList; int chainMaxRange = OriginSystemForShotOrNull.TypeData.BeamChainsOutToTargetsXRange; int chainMinRange = OriginSystemForShotOrNull.TypeData.BeamChainsOutToTargetMinRange; bool drawVisuals = GameSettings_AIW2.Current.GetBool( ArcenBoolSetting_AIW2.DrawBeamWeaponVisuals ) && myPlanet != null && myPlanet.Index == PlayerAccount_AIW2.GetViewingPlanetIndexSafe(); #region next populate the total list of ships we can hit //we do this once only, since there are some categories that we don't want to do over and over again. if ( allValidTargets == null ) allValidTargets = new List(); else allValidTargets.Clear(); PlanetFaction fac; 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 ( (OriginSystemForShotOrNull == null || !OriginSystemForShotOrNull.TypeData.AOEHitsFriendlyTargets) && !myFaction.GetIsHostileTowards( fac ) ) continue; fac.Entities.DoForEntities( delegate ( GameEntity_Squad otherEntity ) { if ( otherEntity.GetDamageForbiddenToThisTargetByTargetingRules( myFaction, TargetOrNull != null && otherEntity.PrimaryKeyID == TargetOrNull.PrimaryKeyID ) ) return DelReturn.Continue; if ( otherEntity.ProtectingShields_NoDamageReduction.Count > 0 || otherEntity.ProtectingShields_ReduceDamage.Count > 0 ) return DelReturn.Continue; //don't chain to under forcefields if ( otherEntity == targetEntity ) return DelReturn.Continue; //don't hit our original target if ( otherEntity.GetCurrentCloakingPoints() > 0 ) return DelReturn.Continue; //don't hit cloaked ships allValidTargets.Add( otherEntity ); return DelReturn.Continue; } ); } #endregion //next remember the first entity that we are hitting entitiesThatWeShouldChainFromThisCycle.Add( targetEntity ); //do the hit logic against the target, or it doesn't seem to get hit at all EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, targetEntity, Context ); FInt percentageForAfterMain = FInt.OneHundred; if ( OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget > FInt.Zero ) { percentageForAfterMain = OriginSystemForShotOrNull.TypeData.AOEAndBeamDamageMultiplierToNonPrimaryTarget; } if ( drawVisuals ) { //note: this cone of beams is for visual purposes only if ( OriginSystemForShotOrNull.TypeData.NumberBeamsToFire > 1 && OriginSystemForShotOrNull.TypeData.DistanceFromCenterForBeamEmission > 0 ) { if ( workingEmissionPoints == null ) workingEmissionPoints = new List(); Mat.FillRingOfPointsAtDistance( originEntity.WorldLocation, ref workingEmissionPoints, OriginSystemForShotOrNull.TypeData.NumberBeamsToFire, OriginSystemForShotOrNull.TypeData.DistanceFromCenterForBeamEmission ); foreach ( ArcenPoint emissionPoint in workingEmissionPoints ) { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.ChainLightning]; if ( lineType != null && originEntity != null && targetEntity != null ) { LineSetWrapper wrapper = originEntity.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = emissionPoint - originEntity.WorldLocation; lineSet.TargetPoint = targetEntity.WorldLocation; lineSet.RemainingTime = 0.4f; lineSet.FromEntity = originEntity; } } } workingEmissionPoints.Clear(); } else //the normal single beam { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.ChainLightning]; if ( lineType != null && originEntity != null && targetEntity != null ) { LineSetWrapper wrapper = originEntity.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = ArcenPoint.ZeroZeroPoint; lineSet.TargetPoint = targetEntity.WorldLocation; lineSet.RemainingTime = 0.4f; lineSet.FromEntity = originEntity; } } } } //how many iterations out do we go? for ( int chainIndex = 0; chainIndex < OriginSystemForShotOrNull.TypeData.BeamChainsOutToTargetsXTimes; chainIndex++ ) { if ( allValidTargets.Count <= 0 ) break; //if no more targets, then stop looking for anything to do if ( entitiesThatWeShouldChainFromThisCycle.Count <= 0 ) break; //if nothing new was hit last cycle, then also stop looking for anything to do if ( remainingTargets <= 0 ) break; //if we already hit too many targets entitiesToChainFromNextTime.Clear(); #region Find All The Hits For Each Chain Source int distance; foreach ( var chainSource in entitiesThatWeShouldChainFromThisCycle ) { if ( remainingTargets <= 0 ) break; //if we already hit too many targets int remainingTargetsForThisSource = maxTargetsPerSource > 0 ? maxTargetsPerSource : 999; ArcenPoint myLoc = chainSource.WorldLocation; for ( int i = allValidTargets.Count - 1; i >= 0; i-- ) { GameEntity_Squad possibleTarget = allValidTargets[i]; if ( possibleTarget.CurrentStateOfMatter != stateofMatter ) continue; distance = chainMaxRange + possibleTarget.DataForMark.Radius + possibleTarget.CalculatedCurrentShieldRadius; if ( !possibleTarget.WorldLocation.GetHasAnyChanceOfBeingInRange( myLoc, distance ) ) continue; int actualDist = possibleTarget.WorldLocation.GetDistanceTo( myLoc, false ); if ( actualDist > distance || actualDist < chainMinRange ) continue; //we got a hit! remainingTargets--; remainingTargetsForThisSource--; entitiesToChainFromNextTime.Add( possibleTarget ); allValidTargets.RemoveAt( i ); #region Do The Hit! EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, possibleTarget, false, targetEntity == possibleTarget ? FInt.OneHundred: percentageForAfterMain, Context ); if ( drawVisuals ) { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.ChainLightning]; if ( lineType != null && chainSource != null && possibleTarget != null ) { LineSetWrapper wrapper = chainSource.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = ArcenPoint.ZeroZeroPoint; lineSet.TargetPoint = possibleTarget.WorldLocation; lineSet.RemainingTime = 0.45f; lineSet.FromEntity = chainSource; } } } if ( remainingTargetsForThisSource <= 0 ) break; //if this source already hit as many as possible if ( remainingTargets <= 0 ) break; //if we already hit too many targets #endregion } } #endregion if ( remainingTargets <= 0 ) break; //if we already hit too many targets if ( entitiesToChainFromNextTime.Count <= 0 ) break; //if no targets hit freshly this time //if there will be another cycle, then record what we should chain from next cycle if ( chainIndex < OriginSystemForShotOrNull.TypeData.BeamChainsOutToTargetsXTimes - 1 ) { entitiesThatWeShouldChainFromThisCycle.Clear(); entitiesThatWeShouldChainFromThisCycle.AddRange( entitiesToChainFromNextTime ); entitiesToChainFromNextTime.Clear(); } } } else //only hits the target { //do the hit logic against the target, or it doesn't seem to get hit at all EntitySimLogic.Instance.DoShotHitLogic( ShotHitNeverNull, OriginSystemForShotOrNull, targetEntity, Context ); if ( GameSettings_AIW2.Current.GetBool( ArcenBoolSetting_AIW2.DrawBeamWeaponVisuals ) && myPlanet != null && myPlanet.Index == PlayerAccount_AIW2.GetViewingPlanetIndexSafe() ) { if ( OriginSystemForShotOrNull.TypeData.NumberBeamsToFire > 1 && OriginSystemForShotOrNull.TypeData.DistanceFromCenterForBeamEmission > 0 ) { if ( workingEmissionPoints == null ) workingEmissionPoints = new List(); Mat.FillRingOfPointsAtDistance( originEntity.WorldLocation, ref workingEmissionPoints, OriginSystemForShotOrNull.TypeData.NumberBeamsToFire, OriginSystemForShotOrNull.TypeData.DistanceFromCenterForBeamEmission ); foreach ( ArcenPoint emissionPoint in workingEmissionPoints ) { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.RingLaser]; if ( lineType != null && originEntity != null && targetEntity != null ) { LineSetWrapper wrapper = originEntity.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = emissionPoint - originEntity.WorldLocation; lineSet.TargetPoint = targetEntity.WorldLocation; lineSet.FromEntity = originEntity; } } } workingEmissionPoints.Clear(); } else { EntityLineType lineType = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.Laser]; if ( lineType != null && originEntity != null && targetEntity != null ) { LineSetWrapper wrapper = originEntity.GetLineSetPairByType_OrCreate( lineType, 0 ); EntityLineSet lineSet = wrapper.GetLineSet(); if ( lineSet != null ) { lineSet.OffetFromShipOrigin = ArcenPoint.ZeroZeroPoint; lineSet.TargetPoint = targetEntity.WorldLocation; lineSet.FromEntity = originEntity; } } } } return true; } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "CheckForShotAOEDetonation error at debugStage " + debugStage + ": " + e, Verbosity.ShowAsError ); } //not an AOE shot return false; } [Obsolete( "SpawnShot is deprecated, please use SpawnShot_ReturnNullIfMPClient instead.", true )] public GameEntity_Shot SpawnShot( EntitySystem System, ArcenPoint StartingLocation, ArcenSimContext Context ) { if ( System == null ) return null; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return null; return SpawnShot( System, typeData.ShotTypeData, StartingLocation, Context ); } [Obsolete( "SpawnShot is deprecated, please use SpawnShot_ReturnNullIfMPClient instead.", true )] public GameEntity_Shot SpawnShot( EntitySystem System, GameEntityTypeData effectiveType, ArcenPoint StartingLocation, ArcenSimContext Context ) { GameEntity_Shot newShot = GameEntity_Shot.CreateNew_CallFromHostOnly( System.ParentEntity.PlanetFaction, effectiveType, System, StartingLocation, Context ); return newShot; } public GameEntity_Shot SpawnShot_ReturnNullIfMPClient( EntitySystem System, ArcenPoint StartingLocation, ArcenSimContext Context ) { if ( System == null ) return null; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return null; return SpawnShot_ReturnNullIfMPClient( System, typeData.ShotTypeData, StartingLocation, Context ); } public GameEntity_Shot SpawnShot_ReturnNullIfMPClient( EntitySystem System, GameEntityTypeData effectiveType, ArcenPoint StartingLocation, ArcenSimContext Context ) { GameEntity_Shot newShot = GameEntity_Shot.CreateNew_CallFromHostOnly( System.ParentEntity.PlanetFaction, effectiveType, System, StartingLocation, Context ); return newShot; } [Obsolete( "SpawnSquadStyleShotOrReturnNull is deprecated, please use SpawnSquadStyleShot_ReturnNullIfMPClient instead.", true )] public GameEntity_Squad SpawnSquadStyleShotOrReturnNull( SquadIDSource IDSource, EntitySystem System, ArcenPoint StartingLocation, ArcenSimContext Context ) { if ( System == null ) return null; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return null; return SpawnSquadStyleShotOrReturnNull( IDSource, System, typeData.ShotTypeData, StartingLocation, Context ); } [Obsolete( "SpawnSquadStyleShotOrReturnNull is deprecated, please use SpawnSquadStyleShot_ReturnNullIfMPClient instead.", true )] public GameEntity_Squad SpawnSquadStyleShotOrReturnNull( SquadIDSource IDSource, EntitySystem System, GameEntityTypeData effectiveType, ArcenPoint StartingLocation, ArcenSimContext Context ) { GameEntity_Squad newShot = GameEntity_Squad.CreateNew_ReturnNullIfMPClient( System.ParentEntity.PlanetFaction, effectiveType, System.ParentEntity.CurrentMarkLevel, System.ParentEntity.FleetMembership.Fleet, 0, StartingLocation, Context ); return newShot; } public GameEntity_Squad SpawnSquadStyleShot_ReturnNullIfMPClient( EntitySystem System, ArcenPoint StartingLocation, ArcenSimContext Context ) { if ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) return null; if ( System == null ) return null; EntitySystemTypeData typeData = System.TypeData; if ( typeData == null ) return null; return SpawnSquadStyleShot_ReturnNullIfMPClient( System, typeData.ShotTypeData, StartingLocation, Context ); } public GameEntity_Squad SpawnSquadStyleShot_ReturnNullIfMPClient( EntitySystem System, GameEntityTypeData effectiveType, ArcenPoint StartingLocation, ArcenSimContext Context ) { if ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) return null; if ( System == null ) return null; GameEntity_Squad parent = System.ParentEntity; if ( parent == null ) return null; PlanetFaction pFaction = parent.PlanetFaction; if ( pFaction == null ) return null; Fleet fleet = parent.GetFleetOrNull_Safe(); if ( fleet == null ) return null; byte markLevel = parent.CurrentMarkLevel; GameEntity_Squad newShot = GameEntity_Squad.CreateNew_ReturnNullIfMPClient( pFaction, effectiveType, markLevel, fleet, 0, StartingLocation, Context ); if ( newShot != null && markLevel > 1 ) newShot.CustomBaseMark = markLevel; return newShot; } #endregion #region DoWormholeTraversalLogic //wormhole transit; transit wormhole public override void DoWormholeTraversalLogic( ArcenSimContext Context ) { if ( CentralVars.DEBUG_TURN_OFF_WORMHOLE_TRAVERSAL ) return; int debugStage = 0; try { debugStage = 10; bool didShipsGoThroughWormhole = false; int currentIndex = -1; ArcenPoint shipWormholePoint = ArcenPoint.ZeroZeroPoint; if ( PlayerAccount.Local != null ) { debugStage = 15; currentIndex = PlayerAccount_AIW2.GetViewingPlanetIndexSafe(); } debugStage = 20; //bool onGalaxyMap = Engine_AIW2.Instance.CurrentGameViewMode != GameViewMode.MainGameView; World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { debugStage = 100; if ( !planet.BattleStatus_ProcessThisSimStep ) return DelReturn.Continue; debugStage = 200; for ( int j = 0; j < planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame.Count; j++ ) { debugStage = 300; GameEntity_Squad entity = planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame[j]; debugStage = 310; EntityOrder order = entity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision( false ); debugStage = 400; if ( order == null || order.TypeData.Type != EntityOrderType.Wormhole ) continue; if ( entity.ActiveHack_Target != 0 ) continue; if ( entity.GetHasBeenDestroyed() || entity.ToBeRemovedAtEndOfThisFrame ) continue; debugStage = 500; Planet myPlanet = entity.Planet; if ( myPlanet != planet ) continue; GameEntity_Other thisSideWormhole = order.GetWormholeToOtherPlanetOrNull( myPlanet ); if ( thisSideWormhole == null ) continue; debugStage = 600; Fleet.Membership fleetMem = entity.FleetMembership; if ( fleetMem == null ) continue; debugStage = 700; PlanetFaction pFaction = entity.PlanetFaction; if ( pFaction == null ) continue; Faction faction = pFaction.Faction; if ( faction == null ) continue; debugStage = 800; GameEntityTypeData.MarkLevelStats entityMarkData = entity.DataForMark; if ( entityMarkData == null ) continue; debugStage = 900; EntityOrderCollection entityOrders = entity.Orders; if ( entityOrders == null ) continue; debugStage = 910; if ( entity.CurrentCountOfTractorsPullingOnThis > 0 || (entity.CurrentlyIAmBeingReverseTractoredByTheseOtherShipsAndThusPullingThem_OrNull != null && entity.CurrentlyIAmBeingReverseTractoredByTheseOtherShipsAndThusPullingThem_OrNull.Count > 0) ) continue; //can't go through wormhole if being tractored Planet targetPlanet = thisSideWormhole.GetLinkedPlanet(); if ( targetPlanet == null || targetPlanet == myPlanet ) continue; FactionType entityFactionType = entity.GetFactionTypeSafe(); switch ( entityFactionType ) { case FactionType.Player: if ( targetPlanet.IsBlockedToPlayerTravelViaWormholes ) { if ( ArcenTime.TimeSinceStartF - EntityOrder.LastTimeReportedOnUnableToGoToPlanet > 3f ) { EntityOrder.LastTimeReportedOnUnableToGoToPlanet = ArcenTime.TimeSinceStartF; World_AIW2.Instance.QueueChatMessageOrCommand( "Apologies, but travel to the planet " + targetPlanet.Name + " is disallowed " + (World_AIW2.Instance.TutorialOrNull == null ? "right now." : "in this tutorial."), ChatType.ShowLocallyOnly, Engine_AIW2.Instance.MainThreadContext ); } entity.Orders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "Wormhole Blocked Bu Planet Status Blocked To Players" ); continue; } break; default: if ( targetPlanet.IsBlockedToNPCTravelViaWormholes ) { entity.Orders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans, "Wormhole Blocked Bu Planet Status Blocked To NPCs" ); continue; } break; } debugStage = 1000; if ( !entity.TypeData.CanPassThroughEnemyForcefields ) { bool foundBlockingShield = false; debugStage = 1100; thisSideWormhole.Planet.DoForEntities( EntityRollupType.ProjectsForcefield, delegate ( GameEntity_Squad shieldGenerator ) { debugStage = 2000; if ( !shieldGenerator.PlanetFaction.Faction.GetIsHostileTowards( faction ) ) return DelReturn.Continue; int distanceThreshold = shieldGenerator.CalculatedCurrentShieldRadius; if ( distanceThreshold <= 0 ) return DelReturn.Continue; debugStage = 2100; distanceThreshold += shieldGenerator.DataForMark.Radius; // not relevant per se, but kind of a stand-in for the radius of the retreating unit; TODO: maybe we need to have this been the largest radius of the units trying to retreat distanceThreshold += thisSideWormhole.TypeData.BaseMark.Radius; if ( shieldGenerator.GetDistanceTo_ExpensiveAccurate( thisSideWormhole, false, false ) > distanceThreshold ) return DelReturn.Continue; foundBlockingShield = true; return DelReturn.Break; } ); debugStage = 2200; if ( foundBlockingShield && !entity.TypeData.PushesEnemyShields && !entity.GetIsCrippled() ) continue; //if the wormhole is blocked by a forcefield, don't let the ships go in (unless they have the norris effect or are crippled) } debugStage = 3000; debugStage = 3100; GameEntity_Other otherSideWormhole = null; PlanetFaction targetPlanetDestinationFaction = null; try { debugStage = 3200; debugStage = 3300; otherSideWormhole = targetPlanet.GetWormholeTo( myPlanet ); if ( otherSideWormhole == null ) { if ( ArcenNetworkAuthority.DesiredStatus != DesiredMultiplayerStatus.Client ) ArcenDebugging.ArcenDebugLogSingleLine( "Was looking for a wormhole on the far side of " + targetPlanet.Name + " from " + myPlanet.Name + " with wormhole on " + thisSideWormhole.Planet.Name + " but couldn't find one. We had one in one direction only.", Verbosity.DoNotShow ); continue; } debugStage = 3400; targetPlanetDestinationFaction = targetPlanet.GetPlanetFactionForFaction( faction ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "Hit exception when trying to move " + entity.ToStringWithPlanetAndOwner() + " to " + targetPlanet.Name + " " + e.ToString(), Verbosity.DoNotShow ); } debugStage = 4000; if ( !didShipsGoThroughWormhole ) { if ( currentIndex == targetPlanet.Index ) { debugStage = 4100; shipWormholePoint = otherSideWormhole.WorldLocation; didShipsGoThroughWormhole = true; } else if ( currentIndex == myPlanet.Index ) { debugStage = 4200; 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 = faction.Type == FactionType.Player; debugStage = 5000; entity.RemoveFromSpatialPartitioning( false, InstancedRendererDeactivationReason.WentThroughWormhole ); entity.UnitForciblyDecloaked = false; //the temporary "force decloak" is unset when a unit goes through a wormhole //AngleDegrees angleToWormhole = Engine_AIW2.Instance.CombatCenter.GetAngleToDegrees( otherSideWormhole.WorldLocation ); //int distanceToWormhole = Engine_AIW2.Instance.CombatCenter.GetDistanceTo( otherSideWormhole.WorldLocation, false ); debugStage = 5100; ArcenPoint exitPoint = otherSideWormhole.WorldLocation; entity.GameSecondEnteredThisPlanet = World_AIW2.Instance.GameSecond; entity.SetWorldLocation( exitPoint ); debugStage = 5200; pFaction.SwitchToFaction( entity, targetPlanetDestinationFaction, true ); entity.AddToNewPlanet( targetPlanet ); this.DoForShipAfterItTransitsWormhole( entity, true ); debugStage = 5300; //clear any decollision orders that were previously in place before going through the wormhole. entityOrders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.OnlyClearDecollisionOrdersAndNothingElse, ClearSource.DoNotClearAnythingExceptDecollision, "DoWormholeTraversalLogic" ); debugStage = 5400; if ( entity.Guarding.Ref == null && entity.GuardingOffsets.Count > 0 && GetShouldEntityUseImplicitMeleeAttackerBehavior( entity, faction, entityOrders ) ) entity.GuardingOffsets.Clear(); // clear melee unit's memory of where to go back to debugStage = 6000; if ( entity.CurrentlyStrongestTractorSourceHittingThese.Count > 0 ) { debugStage = 6100; for ( int k = 0; k < entity.CurrentlyStrongestTractorSourceHittingThese.Count; k++ ) { debugStage = 6200; int id = entity.CurrentlyStrongestTractorSourceHittingThese[k]; GameEntity_Squad tractoredEntity = World_AIW2.Instance.GetEntityByID_Squad( id ); if ( tractoredEntity == null ) continue; debugStage = 6300; if ( tractoredEntity.TypeData.ProjectsForcefield && tractoredEntity.ShieldPointsLost < tractoredEntity.GetMaxShieldPoints() ) continue; // can tractor something with a shield, but you can't actually tow it debugStage = 6400; if ( tractoredEntity.Planet == targetPlanet ) // might already be there if both are still mobile and both are trying to transit the same wormhole continue; debugStage = 6500; tractoredEntity.RemoveFromSpatialPartitioning( false, InstancedRendererDeactivationReason.DraggedThroughWormholeByTractorBeam ); debugStage = 6600; tractoredEntity.PlanetFaction.SwitchToFaction( tractoredEntity, targetPlanet.GetPlanetFactionForFaction( tractoredEntity.PlanetFaction.Faction ), true ); debugStage = 6700; 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++ ) { debugStage = 6800; EntitySystem system = entity.Systems[index]; if ( system.DataForMark.TractorCount <= 0 ) continue; if ( system.ForShortTermPlanning_DisabledReason != ArcenRejectionReason.Unknown ) continue; debugStage = 6900; tractorRange = Math.Max( tractorRange, system.DataForMark.TractorRange ); } debugStage = 7000; //if no tractor beam range found, then probably it is a reverse beam if ( tractorRange <= 0 ) { debugStage = 7100; for ( int index = 0; index < tractoredEntity.Systems.Count; index++ ) { debugStage = 7200; EntitySystem system = tractoredEntity.Systems[index]; if ( system.DataForMark.TractorCount <= 0 ) continue; if ( system.ForShortTermPlanning_DisabledReason != ArcenRejectionReason.Unknown ) continue; debugStage = 7300; tractorRange = Math.Max( tractorRange, system.DataForMark.TractorRange ); } } //if still not found for some reason, then probably a beam shut off. Go ahead and use at least some range if ( tractorRange <= 0 ) tractorRange = 200; debugStage = 8000; AngleDegrees angle = AngleDegrees.Create( (float)Context.RandomToUse.Next( 1, 360 ) ); ArcenPoint point = exitPoint.GetPointAtAngleAndDistance( angle, tractorRange ); debugStage = 8100; if ( Engine_AIW2.Instance.GetIsPointOutsideGravWell_SlowButCorrect( point ) ) { debugStage = 8200; //if it would be outside the gravwell, mirror it to inside the gravwell. //I hope this code path is hit infrequently enough to not cause slowdowns. point = exitPoint.GetPointAtAngleAndDistance( angle.GetOpposite(), tractorRange ); } debugStage = 8300; //if tractor range would put the units outside the gravwell, don't tractoredEntity.SetWorldLocation( point ); debugStage = 8400; tractoredEntity.AddToNewPlanet( targetPlanet ); debugStage = 8500; this.DoForShipAfterItTransitsWormhole( tractoredEntity, false ); } } debugStage = 9000; //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) } debugStage = 9500; planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame.Clear(); return DelReturn.Continue; } ); debugStage = 11200; if ( didShipsGoThroughWormhole ) Engine_AIW2.Instance.PresentationLayer.PlaySoundByType( SFXItemType_Positional.WormholeTransit, shipWormholePoint ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "DoWormholeTraversalLogic error at debugStage " + debugStage + ": " + e, Verbosity.ShowAsError ); } } #endregion #region DoForShipAfterItTransitsWormhole public void DoForShipAfterItTransitsWormhole( GameEntity_Squad Ship, bool MadeTransitOfOwnFreeWill ) { //these things ONLY happen when ships go through wormholes, rather than whenever they are added to a planet from other sources. //so for instance, not when exiting transports or guard posts. This is a useful distinction. if ( Ship == null ) return; #region StateOfMatterToBecomeOnWormholeExit if ( Ship.TypeData.StateOfMatterToBecomeOnWormholeExit != null ) { Ship.CurrentStateOfMatter = Ship.TypeData.StateOfMatterToBecomeOnWormholeExit; if ( Ship.TypeData.ReturnsToDefaultStateOfMatterAfterSecondsFromWormholeExit > 0 ) Ship.GameSecondWillExitStateOfMatter = World_AIW2.Instance.GameSecond + Ship.TypeData.ReturnsToDefaultStateOfMatterAfterSecondsFromWormholeExit; else Ship.GameSecondWillExitStateOfMatter = 0; //stay in there indefinitely, apparently! } #endregion } #endregion #region DoCombatStepForPlanet public override void DoCombatStepForPlanet( Planet planet, ArcenSimContext Context ) { if ( CentralVars.DEBUG_TURN_OFF_COMBAT_STEP_AT_PLANETS ) return; if ( planet == null || !planet.BattleStatus_ProcessThisSimStep ) { return; //this is a background planet battle (or background ships moving around), so skip is this frame } ArcenSimContext DelegateHelper_Context = Context; Context.RandomToUse.ReinitializeWithSeed( World_AIW2.Instance.Network_CurrentFrameNumber + planet.RandomSeed ); int debugStage = 0; try { if ( !CentralVars.DEBUG_TURN_OFF_COMBAT_CENTRAL_STUFF_PER_STEP_LOGIC ) { debugStage = 1000; this.RecalculateAICounterattackForcesStrength( planet ); debugStage = 1200; #region beginning-of-frame processing for per-frame AI variables planet.DoForEntities( delegate ( GameEntity_Squad entity ) { debugStage = 1250; Fleet.Membership fleetMem = entity.FleetMembership; if ( fleetMem == null ) return DelReturn.Continue; PlanetFaction pFaction = entity.PlanetFaction; if ( pFaction == null ) return DelReturn.Continue; Faction faction = pFaction.Faction; if ( faction == null ) return DelReturn.Continue; debugStage = 1300; entity.CalculatedAttackerDamageTotal_LastFrame = entity.CalculatedAttackerDamageTotal_InProgress; debugStage = 1400; entity.CalculatedAttackerDamageTotal_InProgress = 0; debugStage = 1500; if ( faction.Type == FactionType.Player ) { debugStage = 1600; if ( fleetMem != null ) { debugStage = 1700; entity.SetCurrentMarkLevelIfHigherThanCurrent( fleetMem.EffectiveMark, Context ); } } return DelReturn.Continue; } ); debugStage = 1800; if ( planet.WhiteoutRemainingDuration > 0 ) { planet.WhiteoutRemainingDuration -= Engine_Universal.GameDeltaTime; } #endregion #region do per-step logic for each natural object if ( !CentralVars.DEBUG_TURN_OFF_COMBAT_NATURAL_OBJECT_PER_STEP_LOGIC ) { debugStage = 3000; planet.DoForEntities( delegate ( GameEntity_Other entity ) { debugStage = 3100; entity.DoEntityStepLogic_NaturalObject(); return DelReturn.Continue; } ); } #endregion } #region do per-step logic for each ship if ( !CentralVars.DEBUG_TURN_OFF_COMBAT_SHIP_PER_STEP_LOGIC ) { bool doShotsAllInstaHit = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "ShotsAllInstaHit" ); debugStage = 4000; for ( int i = 0; i < planet.Factions.Count; i++ ) { PlanetFaction faction = planet.Factions[i]; if ( faction.Entities.SquadCount <= 0 ) continue; Context.RandomToUse.ReinitializeWithSeed( World_AIW2.Instance.Network_CurrentFrameNumber + planet.RandomSeed + i ); faction.Entities.DoForEntities( delegate ( GameEntity_Squad entity ) { debugStage = 4100; if ( entity == null ) return DelReturn.Continue; debugStage = 4150; bool wasSelected = entity.GetIsSelected(); debugStage = 4200; entity.DoEntityStepLogic_Ship( planet.LocalDeltaTime, DelegateHelper_Context, doShotsAllInstaHit ); //if ( !entity.MovedLastFrame && entity.DecollisionMoveTarget.X != 0 ) // entity.DecollisionMoveTarget = ArcenPoint.ZeroZeroPoint; debugStage = 4300; if ( entity.ToBeRemovedAtEndOfThisFrame || entity.GetHasBeenDestroyed() ) { debugStage = 4400; if ( entity.ExtraStackedSquadsInThis > 0 ) //better late than never... { debugStage = 4500; entity.EjectEntireStackFromMyselfIfPresent( Context, 0, wasSelected ); } } return DelReturn.Continue; } ); } } #endregion #region move each shot on the board if ( !CentralVars.DEBUG_TURN_OFF_COMBAT_SHOT_MOVEMENT ) { debugStage = 6000; planet.DoForEntities( delegate ( GameEntity_Shot entity ) { debugStage = 6100; if ( entity.TypeData == null ) { debugStage = 6200; entity.SetToBeRemovedAtEndOfThisFrameForReason( InstancedRendererDeactivationReason.MissingTypeData ); return DelReturn.Continue; } debugStage = 6300; //UnityEngine.Debug.Log( entity.PrimaryKeyID + ":" + entity.TypeData.InternalName + " " + ( entity.Planet == null ? "null" : entity.Planet.Name ) + " crea " + // entity.SecondsSinceCreation + " rem " + entity.ToBeRemovedAtEndOfThisFrame + " loc " + entity.WorldLocation ); if ( entity.DoEntityStepLogic_Shot( DelegateHelper_Context, planet.LocalDeltaTime ) ) { debugStage = 6400; if ( entity.VisualLinkObject_GenericOnly != null || entity.InstancedRenderer != null ) entity.IHaveDiedSoPleaseDisregardAnyRequestForANewVisualObject = true; debugStage = 6500; entity.DoOnDeathInCombatLogic( DelegateHelper_Context ); debugStage = 6600; entity.SetTarget( null ); // decrements overkill counter, etc debugStage = 6700; entity.SetToBeRemovedAtEndOfThisFrameForReason( InstancedRendererDeactivationReason.ShotHasReachedTargetSoGetRidOfShot ); } return DelReturn.Continue; } ); } #endregion } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( planet.Name + " DoCombatStepForPlanet error at debugStage " + debugStage + ": " + e, Verbosity.ShowAsError ); } DelegateHelper_Context = null; } #endregion #region DoCombatSecond_FromSimBGThread public override void DoCombatSecond_FromSimBGThread( Planet planet, ArcenSimContext Context ) { if ( CentralVars.DEBUG_TURN_OFF_COMBAT_PER_SECOND_PLANET_LOGIC ) return; this.DoAICounterattackForcesPerSecondLogic( planet, Context ); for ( int i = 0; i < planet.Factions.Count; i++ ) planet.Factions[i].DoCombatSecond( Context ); this.CheckForInternalShipDeployment_DroneProducers_FromSimBGThread( planet, Context ); //this.DoForEntities( delegate ( GameEntity_Other entity ) //{ // entity.DoEntitySecondLogic( Context ); // return DelReturn.Continue; //} ); //this.DoForEntities( delegate ( GameEntity_Shot entity ) //{ // entity.DoEntitySecondLogic( Context ); // return DelReturn.Continue; //} ); Int16 reinforcementLocationCount = 0; planet.DoForEntities( delegate ( GameEntity_Squad entity ) { entity.DoEntitySecondLogic_FromSimBGThread( Context ); if ( entity.TypeData.IsReinforcementLocation ) reinforcementLocationCount++; return DelReturn.Continue; } ); if ( reinforcementLocationCount > planet.MaxReinforcementPlacesEverSeenHere ) planet.MaxReinforcementPlacesEverSeenHere = reinforcementLocationCount; } #endregion #region CheckForInternalShipDeployment_DroneProducers_FromSimBGThread private void CheckForInternalShipDeployment_DroneProducers_FromSimBGThread( Planet planet, ArcenSimContext Context ) { if ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) return; planet.DoForEntities( EntityRollupType.DroneProducer, delegate ( GameEntity_Squad entity ) { Fleet.Membership fleetMem = entity.FleetMembership; if ( fleetMem == null ) return DelReturn.Continue; PlanetFaction pFaction = entity.PlanetFaction; if ( pFaction == null ) return DelReturn.Continue; Faction faction = pFaction.Faction; if ( faction == null ) return DelReturn.Continue; GameEntityTypeData.MarkLevelStats entityMarkData = entity.DataForMark; if ( entityMarkData == null ) return DelReturn.Continue; try { //ArcenDebugging.ArcenDebugLogSingleLine( entity.TypeData.InternalName + " DeployDroneContents? " + fleetMem.Fleet.GetDroneContentsCount(), Verbosity.DoNotShow ); if ( fleetMem.Fleet.GetDroneContentsCount() <= 0 ) return DelReturn.Continue; } catch //this would be a cross-threading issue { return DelReturn.Continue; } bool isDeploying = false; PlanetFaction pFac = pFaction; if ( pFac != null ) { pFac.DoForRelatedFactions( FactionRelationship.FactionsIAmHostileTowards, delegate ( PlanetFaction otherFaction ) { otherFaction.Entities.DoForEntities( EntityRollupType.Combatants, delegate ( GameEntity_Squad otherEntity ) { isDeploying = true; return DelReturn.Break; } ); if ( isDeploying ) return DelReturn.Break; return DelReturn.Continue; } ); //ArcenDebugging.ArcenDebugLogSingleLine( entity.TypeData.InternalName + " DeployDroneContents? isDeploying" + isDeploying, Verbosity.DoNotShow ); } if ( isDeploying ) entity.DeployDroneContents_ONLYCallFromSimBGThread( Context ); return DelReturn.Continue; } ); } #endregion #region DoAICounterattackForcesPerSecondLogic public void DoAICounterattackForcesPerSecondLogic( Planet planet, ArcenSimContext Context ) { if ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) return; //don't do this on clients int debugStage = 0; try { debugStage = 100; #region Spend any of the AICounterattackUnspentBudget if it is there if ( planet.AICounterattackUnspentBudget > FInt.Zero ) { debugStage = 200; if ( planet.ShipGroup_WavesFromHere_Normal != null ) { debugStage = 300; GameEntityTypeData typeToAdd = planet.ShipGroup_WavesFromHere_Normal.DrawBag.PickRandomItemAndReplace( Context.RandomToUse ); debugStage = 350; int loopCount = 30; while ( typeToAdd == null && loopCount-- > 0 ) typeToAdd = planet.ShipGroup_WavesFromHere_Normal.DrawBag.PickRandomItemAndReplace( Context.RandomToUse ); debugStage = 400; bool madeAnyAdditions = false; int maxToAddAtOnce = 100; while ( typeToAdd != null && typeToAdd.CostForAIToPurchase < planet.AICounterattackUnspentBudget && maxToAddAtOnce > 0 ) { debugStage = 500; if ( !planet.AICounterattackForces.GetHasKey( typeToAdd ) ) planet.AICounterattackForces[typeToAdd] = 1; else planet.AICounterattackForces[typeToAdd]++; debugStage = 600; planet.AICounterattackUnspentBudget -= typeToAdd.CostForAIToPurchase; maxToAddAtOnce--; madeAnyAdditions = true; } debugStage = 700; if ( madeAnyAdditions ) this.RecalculateAICounterattackForcesStrength( planet ); } } #endregion debugStage = 1000; if ( planet.PrecalculatedAICounterattackForcesStrength <= 0 ) return; if ( planet.AICounterAttacksCurrentlyStalled ) return; if ( planet.AICounterAttacksNotSufficientToTryToSend ) return; debugStage = 1100; planet.AICountdownTimerForCounterattack--; debugStage = 1100; if ( planet.AICountdownTimerForCounterattack <= 0 ) { debugStage = 2000; //ArcenDebugging.ArcenDebugLogSingleLine( "START trying to launch counterattack!", Verbosity.DoNotShow ); PlanetFaction pFactionToUse = null; debugStage = 2100; byte bestMarkLevelOfPresent = planet.MarkLevelForAIOnly.Ordinal; debugStage = 2200; int counterAttackEnablerCount = 0; planet.DoForEntities( EntityRollupType.AICounterattackEnablers, delegate ( GameEntity_Squad entity ) { PlanetFaction localPFaction = entity.PlanetFaction; if ( localPFaction == null ) return DelReturn.Continue; Faction faction = localPFaction.Faction; if ( faction == null ) return DelReturn.Continue; counterAttackEnablerCount++; debugStage = 2300; if ( localPFaction != null && faction.Type == FactionType.AI ) { debugStage = 2400; pFactionToUse = localPFaction; debugStage = 2500; if ( faction.CurrentGeneralMarkLevel > bestMarkLevelOfPresent ) bestMarkLevelOfPresent = faction.CurrentGeneralMarkLevel; } return DelReturn.Continue; } ); debugStage = 2600; //missing a planet faction to spawn from, which means the player won! if ( pFactionToUse == null ) { debugStage = 2700; planet.PrecalculatedAICounterattackForcesStrength = 0; planet.AICounterattackForces.Clear(); planet.AICountdownTimerForCounterattack = 0; planet.AICounterattackToBeStalledByPlayerStrengthOf = 999999; planet.PlayerStrengthHereForBlockingAICounterAttacks = 0; //ArcenDebugging.ArcenDebugLogSingleLine( "FAIL trying to launch counterattack because missing planet faction!", Verbosity.DoNotShow ); return; } debugStage = 3000; AngleDegrees angle = AngleDegrees.Create( (float)Context.RandomToUse.Next( 1, 360 ) ); ArcenPoint center = Engine_AIW2.Instance.CombatCenter; float warpInMultiplier = 0.7f; debugStage = 3100; ArcenPoint spawnLocation = center.GetPointAtAngleAndDistance( angle, (int)(ExternalConstants.Instance.DistanceScale_GravwellRadius * warpInMultiplier) ); ArcenPoint WarpInStart = center.GetPointAtAngleAndDistance( angle, ExternalConstants.Instance.DistanceScale_GravwellRadius ); debugStage = 3200; int countSpawned = 0; int strengthSpawned = 0; ArcenSparseLookupPair pair; debugStage = 3300; for ( int i = 0; i < planet.AICounterattackForces.GetPairCount(); i++ ) { debugStage = 3400; pair = planet.AICounterattackForces.GetPairByIndex( i ); debugStage = 3500; if ( pair.Value > 0 ) { debugStage = 3600; int numberToAdd = pair.Value; while ( numberToAdd > 0 ) { debugStage = 3700; GameEntity_Squad entity = GameEntity_Squad.CreateNew_ReturnNullIfMPClient( pFactionToUse, pair.Key, pair.Key.MarkFor( bestMarkLevelOfPresent ), pFactionToUse.Faction.LooseFleet, 0, //no differentiated sub-groups on loose fleets spawnLocation, Context ); debugStage = 3800; //entity.SetSpawnLerpStart( WarpInStart ); //entity.SetSpawnLerpDestination( spawnLocation ); entity.spawnVis = SpawnVisualization.WarpIn; debugStage = 3900; entity.Orders.SetBehaviorDirectlyInSim( EntityBehaviorType.Attacker_Full, -1 ); numberToAdd--; debugStage = 4000; if ( numberToAdd >= 9 ) { debugStage = 4100; entity.AddOrSetExtraStackedSquadsInThis( 9, false ); numberToAdd -= 9; } else if ( numberToAdd > 0 ) { debugStage = 4200; entity.AddOrSetExtraStackedSquadsInThis( (Int16)numberToAdd, false ); numberToAdd = 0; } debugStage = 4300; countSpawned += 1 + entity.ExtraStackedSquadsInThis; strengthSpawned += ((1 + entity.ExtraStackedSquadsInThis) * entity.GetStrengthPerSquad()); } } } //ArcenDebugging.ArcenDebugLogSingleLine( "MORE countSpawned " + countSpawned + " strengthSpawned " + strengthSpawned + // " AICounterattackForces.GetPairCount " + planet.AICounterattackForces.GetPairCount(), Verbosity.DoNotShow ); debugStage = 5000; Engine_AIW2.Instance.PresentationLayer.PlaySoundByType( SFXItemType_NonPositional.PlayerFailsAtSomething ); //the spawning is done! debugStage = 5100; planet.PrecalculatedAICounterattackForcesStrength = 0; debugStage = 5200; planet.AICounterattackForces.Clear(); debugStage = 5300; if ( countSpawned > 0 ) { debugStage = 5400; if ( ArcenNetworkAuthority.GetIsHostMode() ) World_AIW2.Instance.QueueChatMessageOrCommand( "AI Counterattack at " + planet.Name + ": " + countSpawned + " ships of total strength " + (strengthSpawned / 1000f).ToString( "0.#" ), ChatType.LogToCentralChat, "MaraudersAttacking", Context ); } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "Exception in DoAICounterattackForcesPerSecondLogic at debugStage " + debugStage + ". Exception: " + e, Verbosity.ShowAsError ); } } #endregion #region RecalculateAICounterattackForcesStrength public void RecalculateAICounterattackForcesStrength( Planet planet ) { if ( Engine_AIW2.Instance.IsTestChamber ) return; int debugStage = 0; try { debugStage = 1000; if ( planet.MarkLevelForAIOnly == null ) planet.MarkLevelForAIOnly = Balance_MarkLevelTable.Instance.RowsByOrdinal[1]; debugStage = 1050; int bestMarkLevelOfPresent = planet.MarkLevelForAIOnly.Ordinal; bool foundAnyEnablers = false; planet.AIStrengthRequiredToSend = 0; debugStage = 1100; planet.DoForEntities( EntityRollupType.AICounterattackEnablers, delegate ( GameEntity_Squad entity ) { Fleet.Membership fleetMem = entity.FleetMembership; if ( fleetMem == null ) return DelReturn.Continue; PlanetFaction pFaction = entity.PlanetFaction; if ( pFaction == null ) return DelReturn.Continue; Faction faction = pFaction.Faction; if ( faction == null ) return DelReturn.Continue; GameEntityTypeData.MarkLevelStats entityMarkData = entity.DataForMark; if ( entityMarkData == null ) return DelReturn.Continue; if ( pFaction != null && faction.Type == FactionType.AI ) { foundAnyEnablers = true; if ( faction.CurrentGeneralMarkLevel > bestMarkLevelOfPresent ) bestMarkLevelOfPresent = faction.CurrentGeneralMarkLevel; if ( planet.AIStrengthRequiredToSend < faction.CounterattackMinStrengthFromExternal ) planet.AIStrengthRequiredToSend = faction.CounterattackMinStrengthFromExternal; } return DelReturn.Continue; } ); debugStage = 1200; if ( !foundAnyEnablers ) { planet.PrecalculatedAICounterattackForcesStrength = 0; planet.AICounterattackForces.Clear(); planet.AICountdownTimerForCounterattack = 0; planet.AICounterattackToBeStalledByPlayerStrengthOf = 999999; planet.PlayerStrengthHereForBlockingAICounterAttacks = 0; return; } debugStage = 1300; planet.PrecalculatedAICounterattackForcesStrength = 0; ArcenSparseLookupPair pair; for ( int i = 0; i < planet.AICounterattackForces.GetPairCount(); i++ ) { pair = planet.AICounterattackForces.GetPairByIndex( i ); if ( pair.Value > 0 ) planet.PrecalculatedAICounterattackForcesStrength += (pair.Key.GetForMark( bestMarkLevelOfPresent ).StrengthPerSquad_CalculatedWithNullFleetMembership * pair.Value); } debugStage = 1400; planet.AICounterattackToBeStalledByPlayerStrengthOf = planet.PrecalculatedAICounterattackForcesStrength / 2; debugStage = 1500; planet.PlayerStrengthHereForBlockingAICounterAttacks = 0; Faction fac; for ( int i = 0; i < World_AIW2.Instance.Factions.Count; i++ ) { fac = World_AIW2.Instance.Factions[i]; if ( fac.Type != FactionType.Player && (fac.SpecialFactionData == null || !fac.SpecialFactionData.CountsAsPartOfPlayerStrengthAtPlanetForCounterattackBlocking) ) continue; ShortRangePlanning_StrengthData_PlanetFaction_Stance selfData = planet.GetPlanetFactionForFaction( fac ).DataByStance[FactionStance.Self]; planet.PlayerStrengthHereForBlockingAICounterAttacks += selfData.TotalStrength; } debugStage = 1600; if ( planet.PlayerStrengthHereForBlockingAICounterAttacks >= planet.AICounterattackToBeStalledByPlayerStrengthOf ) { planet.AICountdownTimerForCounterattack = ExternalConstants.Instance.TimeForCounterattackTimer; planet.AICounterAttacksCurrentlyStalled = true; } else planet.AICounterAttacksCurrentlyStalled = false; debugStage = 1700; if ( planet.AIStrengthRequiredToSend > planet.PrecalculatedAICounterattackForcesStrength ) planet.AICounterAttacksNotSufficientToTryToSend = true; else planet.AICounterAttacksNotSufficientToTryToSend = false; } catch ( Exception e ) { ArcenDebugging.ArcenDebugLog( "RecalculateAICounterattackForcesStrength exception at debugStage " + debugStage + ", exception: " + e, Verbosity.ShowAsError ); } } #endregion public override void DoPerFrameWhilePaused( ArcenSimContext Context ) { if ( CentralVars.DEBUG_TURN_OFF_WORLD_STEP_LOGIC ) return; if ( Engine_Universal.RunStatus == RunStatus.GameStart ) return; DoFleetPerStepCalculationsOnly( Context, false ); } #region DoWorldStepLogic_FromSimBGThread public override void DoWorldStepLogic_FromSimBGThread( ArcenSimContext Context ) { if ( CentralVars.DEBUG_TURN_OFF_WORLD_STEP_LOGIC ) return; if ( Engine_Universal.RunStatus == RunStatus.GameStart ) return; World_AIW2.Instance.OnServer_FramesStoredUpForNextBatch++; World_AIW2.CurrentSimCycleSlow = GameEntity_Base.CalculateSimCycleFromInt_Slow( World_AIW2.Instance.Network_CurrentFrameNumber ); World_AIW2.CurrentSimCycleFast = GameEntity_Base.CalculateSimCycleFromInt_Fast( World_AIW2.Instance.Network_CurrentFrameNumber ); DoVeryFirstSimStepCalculationsLogicOnPlanets( Context ); DoGlobalPlayerPerStepLogic( Context ); DoWorldStepLogic_ProcessArbitraryAmountOfTime( Context ); DoWorld_Second_PerSecondLogic( Context ); DoFleetPerStepCalculationsOnly( Context, true ); DoScenarioPerStepLogic( Context ); DoFactionPerStepLogic( Context ); DoPlanetPerStepLogic( Context ); DoCombatPerStepLogic( Context ); DoRemoveDeadEntitiesLogic( Context ); DoPlanetMovementAndDestructionLogic( Context ); DoShipAILogic( Context ); DoWorldSuffixLogic( Context ); if ( Engine_AIW2.Instance.CurrentGameViewMode == GameViewMode.MainGameView ) World_AIW2.ClearExpiredLineSets(); else World_AIW2.ClearAllLineSets(); } public void DoVeryFirstSimStepCalculationsLogicOnPlanets( ArcenSimContext Context ) { int currentBackgroundStep = World_AIW2.Instance.Network_CurrentFrameNumber % 10; bool doCoarseProcessing = AIWar2GalaxySettingTable.GetIsBoolSettingEnabledByName_DuringGame( "CoarseProcessBackgroundPlanets" ); #region VeryFirstSimStepcalculations Logic On Planets World_AIW2.Instance.DoForPlanetsParallel( false, delegate ( Planet planet ) { planet.VeryFirstSimStepcalculations_Multithreaded( currentBackgroundStep, doCoarseProcessing ); return DelReturn.Continue; } ); #endregion } public void DoGlobalPlayerPerStepLogic( ArcenSimContext Context ) { Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "GlobalPlayerPerStepLogic" ); World_AIW2.Instance.GlobalPlayerPerStepLogic( Context ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "GlobalPlayerPerStepLogic" ); } public void DoWorldStepLogic_ProcessArbitraryAmountOfTime( ArcenSimContext Context ) { Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "WorldStepLogic_ProcessArbitraryAmountOfTime" ); TimeBasedPoolBase.ProcessOneSecondOfDataForAllPoolsIfEnoughTimeHasPassed(); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "WorldStepLogic_ProcessArbitraryAmountOfTime" ); } public void DoWorld_Second_PerSecondLogic( ArcenSimContext Context ) { #region Per Second Logic Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_Second" ); //Engine_Universal.BeginProfilerSample( "world-second" ); Engine_Universal.BeginStopwatchSegment( PerformanceSegment.World_Second ); World_AIW2.Instance.ProgressTowardsNextFullSecond += World_AIW2.Instance.SimulationProfile.SecondsPerFrameSim_GlobalOnly_NotForPlanetsOrCombat; World_AIW2.Instance.IsFirstFrameOfSecond = false; while ( World_AIW2.Instance.ProgressTowardsNextFullSecond >= FInt.One ) { World_AIW2.Instance.ProgressTowardsNextFullSecond -= FInt.One; DoWorldSecondLogic_FromSimBGThread( Context ); World_AIW2.Instance.IsFirstFrameOfSecond = true; } Engine_Universal.EndStopwatchSegment( PerformanceSegment.World_Second ); //Engine_Universal.EndProfilerSample( "world-second" ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_Second" ); #endregion } public void DoFleetPerStepCalculationsOnly( ArcenSimContext Context, bool IsFromMainSimulation ) { #region Fleet Per Step Logic Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Fleet_Step" ); if ( IsFromMainSimulation ) { //first clear out all the list of player fleets at the planet World_AIW2.Instance.DoForPlanetsParallel( true, delegate ( Planet planet ) //include destroyed planets { planet.PlayerFleetsAtPlanet_Prior.Clear(); return DelReturn.Continue; } ); } Faction localPlayerFactionOrNull = World_AIW2.Instance.GetLocalPlayerFactionOrNull(); //Engine_Universal.BeginProfilerSample( "fleet-step" ); for ( int i = 0; i < World_AIW2.Instance.Fleets.Count; i++ ) World_AIW2.Instance.Fleets[i].PerFrame_CalculateEffectiveFleetData( Context, localPlayerFactionOrNull, IsFromMainSimulation ); if ( IsFromMainSimulation ) { World_AIW2.Instance.DoForPlanetsParallel( true, delegate ( Planet planet ) //include destroyed planets { List workingFleets = planet.PlayerFleetsAtPlanet_Prior; planet.PlayerFleetsAtPlanet_Prior = planet.PlayerFleetsAtPlanet_Current; planet.PlayerFleetsAtPlanet_Current = workingFleets; return DelReturn.Continue; } ); } //Engine_Universal.EndProfilerSample( "fleet-step" ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Fleet_Step" ); #endregion } public void DoScenarioPerStepLogic( ArcenSimContext Context ) { #region Scenario Per Step Logic IScenarioImplementation scenarioImp = World_AIW2.Instance.GetScenarioImplementationSafe_OrNull(); if ( scenarioImp == null ) return; Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Scenario_Step" ); //Engine_Universal.BeginProfilerSample( "scenario-step" ); try { scenarioImp.DoPerSimStepLogic_OnMainThreadAndPartOfSim( Context ); } catch( Exception e ) { ScenarioData scenario = World_AIW2.Instance.GetScenarioSafe_OrNull(); ArcenDebugging.ArcenDebugLogSingleLine( scenario?.InternalName + " Scenario Exception in scenario DoPerSimStepLogic_OnMainThreadAndPartOfSim: " + e, Verbosity.ShowAsError ); } //Engine_Universal.EndProfilerSample( "scenario-step" ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Scenario_Step" ); #endregion } public void DoFactionPerStepLogic( ArcenSimContext Context ) { #region Faction Per Step Logic Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Faction_Step" ); //Engine_Universal.BeginProfilerSample( "faction-step" ); Engine_Universal.BeginStopwatchSegment( PerformanceSegment.World_Side ); World_AIW2.Instance.DoForFactions( delegate ( Faction faction ) { faction.DoFactionStepLogic_Stage1( Context ); return DelReturn.Continue; } ); World_AIW2.Instance.DoForFactions( delegate ( Faction faction ) { faction.DoFactionStepLogic_Stage2( Context ); return DelReturn.Continue; } ); World_AIW2.Instance.DoForFactions( delegate ( Faction faction ) { faction.Safe_DoPerSimStepLogic_OnMainThreadAndPartOfSim( Context ); return DelReturn.Continue; } ); Engine_Universal.EndStopwatchSegment( PerformanceSegment.World_Side ); //Engine_Universal.EndProfilerSample( "faction-step" ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Faction_Step" ); #endregion } public void DoPlanetPerStepLogic( ArcenSimContext Context ) { Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Planet_Step" ); #region countOfPlanetsLostByAI int countOfPlanetsLostByAI = 0; World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { //was originally owned by an AI if ( planet.InitialOwningAIFactionIndex >= 0 ) { Faction owner = planet.GetControllingFaction(); if ( owner == null || owner.Type != FactionType.AI ) countOfPlanetsLostByAI++; } return DelReturn.Continue; } ); #endregion //Engine_Universal.BeginProfilerSample( "planet-step" ); Engine_Universal.BeginStopwatchSegment( PerformanceSegment.World_Combat ); World_AIW2.Instance.DoForPlanetsParallel( false, delegate ( Planet planet ) { planet.RecalculateOwnershipsIfBlank(); planet.CheckForOwnershipChange(); planet.CalculateAISentinelAlertStatusAndRelatedBits( countOfPlanetsLostByAI ); if ( !planet.BattleStatus_ProcessThisSimStep ) return DelReturn.Continue; for ( int i = 0; i < planet.Factions.Count; i++ ) planet.Factions[i].DoPrePlanetFactionStepCleanup_Multithreaded(); for ( int i = 0; i < planet.Factions.Count; i++ ) planet.Factions[i].DoPlanetFactionStepLogic_Multithreaded(); return DelReturn.Continue; } ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "Planet_Step" ); } public void DoCombatPerStepLogic( ArcenSimContext Context ) { Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "DoCombatStep" ); World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { EntitySimLogic.Instance.DoCombatStepForPlanet( planet, Context ); return DelReturn.Continue; } ); Engine_Universal.EndStopwatchSegment( PerformanceSegment.World_Combat ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "DoCombatStep" ); //Engine_Universal.EndProfilerSample( "planet-step" ); } public void DoRemoveDeadEntitiesLogic( ArcenSimContext Context ) { #region Remove dead entities Engine_Universal.BeginProfilerSample( "cleanup" ); Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_Cleanup" ); Engine_Universal.BeginStopwatchSegment( PerformanceSegment.World_Cleanup ); World_AIW2.Instance.CheckForActuallyGettingRidOfRemovedEntities(); Engine_Universal.EndStopwatchSegment( PerformanceSegment.World_Cleanup ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_Cleanup" ); Engine_Universal.EndProfilerSample( "cleanup" ); #endregion } public void DoPlanetMovementAndDestructionLogic( ArcenSimContext Context ) { #region Planet Movement and Destruction //Note that this must be done here so it won't race with any other threads UpdateNomadPlanets( Context ); DestroyPlanetsIfNecessary( Context ); #endregion } public void DoShipAILogic( ArcenSimContext Context ) { #region Ship AI //Engine_Universal.BeginProfilerSample( "unit ai" ); Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_EntityAI" ); Engine_Universal.BeginStopwatchSegment( PerformanceSegment.World_EntityAI ); World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { if ( !planet.BattleStatus_ProcessThisSimStep ) return DelReturn.Continue; planet.DoForEntities( delegate ( GameEntity_Squad entity ) { if ( entity == null ) return DelReturn.Continue; EntitySimLogic.Instance.ReevaluateUnitOrders( Context, entity ); return DelReturn.Continue; } ); return DelReturn.Continue; } ); Engine_Universal.EndStopwatchSegment( PerformanceSegment.World_EntityAI ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_EntityAI" ); //Engine_Universal.EndProfilerSample( "unit ai" ); #endregion } public void DoWorldSuffixLogic( ArcenSimContext Context ) { //foreach ( GameEntity_Shot entity in this.CentralEntityLookup_Shot.Values ) //{ // UnityEngine.Debug.Log( entity.PrimaryKeyID + ":" + entity.TypeData.InternalName + " " + ( entity.Planet == null ? "null" : entity.Planet.Name + " contains " // pFaction.Entities.Contains( entity ) ) + " crea " + // entity.SecondsSinceCreation + " rem " + entity.ToBeRemovedAtEndOfThisFrame + " loc " + entity.WorldLocation ); //} int debugStage = 0; try { debugStage = 100; //Engine_Universal.BeginProfilerSample( "world-suffix" ); Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_Suffix" ); Engine_Universal.BeginStopwatchSegment( PerformanceSegment.World_Suffix ); debugStage = 200; #region check for player victory if ( World.Instance.ConclusionType == CampaignConclusionType.NotConcluded && !World_AIW2.Instance.InSetupPhase && !Engine_AIW2.Instance.IsTestChamber ) { debugStage = 300; bool foundAnyAIKingUnits = false; World_AIW2.Instance.DoForEntities( EntityRollupType.KingUnitsOnly, delegate ( GameEntity_Squad entity ) { debugStage = 400; if ( !entity.TypeData.PlayerWinsIfAllAreDestroyed ) return DelReturn.Continue; foundAnyAIKingUnits = true; return DelReturn.Break; } ); debugStage = 500; if ( !foundAnyAIKingUnits && !World_AIW2.Instance.GetIsTutorial() ) { debugStage = 600; { //Handle achievements IScenarioImplementation scenarioImp = World_AIW2.Instance.GetScenarioImplementationSafe_OrNull(); if ( scenarioImp != null ) scenarioImp.DoOnPlayerVictory( Context ); debugStage = 700; //note: CP_AIDef_Final should already handle this, now. //World_AIW2.Instance.QueueChatMessageOrCommand( "You have won!", JournalEntryImportance.NeverDiscard, "", Context ); World_AIW2.Instance.DoConclusionOfGame( CampaignConclusionType.Won ); } } } #endregion debugStage = 800; //check for wormhole traversal EntitySimLogic.Instance.DoWormholeTraversalLogic( Context ); debugStage = 900; Engine_Universal.EndStopwatchSegment( PerformanceSegment.World_Suffix ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "World_Suffix" ); //Engine_Universal.EndProfilerSample( "world-suffix" ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLog( "Exception in DoWorldSuffixLogic, debugStage " + debugStage + ", Exception: " + e, Verbosity.ShowAsError ); } } #endregion #region Nomad Planets public static List PlanetsNearNomads = new List(); public static IWormholePlacer HomeworldPlacer = null; public static List NomadPlacers = null; public static List NomadPlanetList = new List(); public void UpdateNomadPlanets( ArcenSimContext Context ) { if ( ArcenNetworkAuthority.DesiredStatus == DesiredMultiplayerStatus.Client ) return; //Note code needs to run here because Galaxy Links will change, which will break things if it's run as a sim stage 3 int debugCode = 0; try { if ( !World_AIW2.Instance.HasEverBeenUnpaused ) return; NomadPlanetList.Clear(); World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { if ( planet.TypeData.Type == PlanetType.Nomad ) NomadPlanetList.Add( planet ); return DelReturn.Continue; } ); if ( HomeworldPlacer == null || NomadPlacers == null ) { debugCode = 100; //just copied from MapGeneration.cs int baseDistance = ExternalConstants.Instance.DistanceScale_GravwellRadius; int maxDistance = (baseDistance * FInt.FromParts( 0, 750 )).IntValue; int minDistance = (baseDistance * FInt.FromParts( 0, 250 )).IntValue; int middleDistance_1 = (baseDistance * FInt.FromParts( 0, 400 )).IntValue; int middleDistance_2 = (baseDistance * FInt.FromParts( 0, 600 )).IntValue; if ( HomeworldPlacer == null ) HomeworldPlacer = new WormholePlacer_Default( maxDistance, maxDistance ); if ( NomadPlacers == null ) { NomadPlacers = new List(); NomadPlacers.Add( new WormholePlacer_Default( middleDistance_1, middleDistance_1 ) ); NomadPlacers.Add( new WormholePlacer_Default( middleDistance_1, maxDistance ) ); NomadPlacers.Add( new WormholePlacer_Default( middleDistance_1, middleDistance_2 ) ); } } debugCode = 200; for ( int i = 0; i < NomadPlanetList.Count; i++ ) { debugCode = 300; Planet planet = NomadPlanetList[i]; if ( planet.IsDisabledNomad ) continue; Faction nomadFaction = FactionUtilityMethods.GetNomadPlanetFaction(); if ( planet.TimeForNextMove == -1 ) { planet.TimeForNextMove = World_AIW2.Instance.GameSecond + SpecialFaction_NomadPlanets.InitialMoveTime + Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, SpecialFaction_NomadPlanets.VarianceBetweenMoveTimes ); if ( World_AIW2.Instance.CurrentGalaxy.IsNomadGalaxy ) planet.TimeForNextMove += 400 + Context.RandomToUse.Next(0, 800); //nomad planets now take a bit longer to start moving if ( nomadFaction != null && nomadFaction.Ex_MinorFactionCommon_GetPrimitives( ExternalDataRetrieval.CreateIfNotFound ).DebugMode ) planet.TimeForNextMove = World_AIW2.Instance.GameSecond + 20; } Planet targetPlanet = World_AIW2.Instance.GetPlanetByIndex( planet.NomadTargetPlanetIdx ); //Update this planet if necessary if ( planet.TimeForNextMove <= World_AIW2.Instance.GameSecond || //if it's time to move ( targetPlanet != null && planet.SecondsTillNomadCrashes <= 0 ) ) //or if this planet is crashing now { debugCode = 400; if ( targetPlanet != null ) { debugCode = 410; //we are en route to crash into another planet if ( planet.SecondsTillNomadCrashes <= 0 ) { //We have just hit the planet planet.ToBeDestroyed = true; targetPlanet.ToBeDestroyed = true; debugCode = 420; continue; } } debugCode = 430; //move this planet in the galaxy ArcenPoint newLocation = FindNextNomadPoint( planet, Context, NomadPlanetList.Count ); //now find an actual safe spot planet.GalaxyLocation = newLocation; planet.Network_HostOnly_NeedToSyncPlanetPositionAndLinksAndWormholesToClients = true; //find nearby planets, then update links. Note this isn't the most efficient code, but nomads never link to more than //4 planets, so shouldn't be too bad. int radiusForNearNeighbors = 100; int minPlanetsForNomadConnection = 2; int maxPlanetsForNomadConnection = 4; do { debugCode = 440; PlanetsNearNomads.Clear(); World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet otherPlanet ) { debugCode = 450; if ( otherPlanet == planet ) return DelReturn.Continue; if ( otherPlanet.PopulationType == PlanetPopulationType.DarkZenith && otherPlanet.GetControllingOrInfluencingFaction().Implementation as SpecialFaction_DarkZenith != null ) { //this is a DZ planet that warped in; make sure they are ready to link to the galaxy before linking them DarkZenithGlobalData dzData = otherPlanet.GetControllingOrInfluencingFaction().GetDarkZenithGlobalDataExt( ExternalDataRetrieval.CreateIfNotFound ); if ( !dzData.HasLinkedPlanets ) return DelReturn.Continue; } if ( Mat.DistanceBetweenPointsImprecise( planet.GalaxyLocation, otherPlanet.GalaxyLocation ) < radiusForNearNeighbors ) PlanetsNearNomads.Add( otherPlanet ); return DelReturn.Continue; } ); radiusForNearNeighbors += 100; } while ( PlanetsNearNomads.Count <= minPlanetsForNomadConnection ); debugCode = 500; if ( PlanetsNearNomads.Count > maxPlanetsForNomadConnection ) { //if we have too many, cull the weak PlanetsNearNomads.Sort( delegate ( Planet Left, Planet Right ) { int lDistance = Mat.DistanceBetweenPointsImprecise( planet.GalaxyLocation, Left.GalaxyLocation ); int rDistance = Mat.DistanceBetweenPointsImprecise( planet.GalaxyLocation, Left.GalaxyLocation ); return lDistance.CompareTo( rDistance ); } ); PlanetsNearNomads.RemoveRange( maxPlanetsForNomadConnection - 1, PlanetsNearNomads.Count - maxPlanetsForNomadConnection ); } bool planetLinksUpdated = false; debugCode = 600; //remove unecessary old links, then add new links planet.DoForLinkedNeighbors( false, delegate ( Planet otherPlanet ) { debugCode = 700; if ( !PlanetsNearNomads.Contains( otherPlanet ) ) { debugCode = 710; planet.Network_HostOnly_NeedToSyncPlanetPositionAndLinksAndWormholesToClients = true; otherPlanet.Network_HostOnly_NeedToSyncPlanetPositionAndLinksAndWormholesToClients = true; planet.RemoveLinkTo( otherPlanet ); //also destroy wormhole objects GameEntity_Other myWormhole = otherPlanet.GetWormholeTo( planet ); GameEntity_Other otherWormhole = planet.GetWormholeTo( otherPlanet ); debugCode = 720; //these null checks are defensive code against what seems to be an MP-specific bug where we aren't //creating the right wormholes when a nomad moves //Note that this definitely has been observed with two adjacent nomad planets if ( myWormhole != null ) myWormhole.SetToBeRemovedAtEndOfThisFrameForReason( InstancedRendererDeactivationReason.RemoveWormholes ); if ( otherWormhole != null ) otherWormhole.SetToBeRemovedAtEndOfThisFrameForReason( InstancedRendererDeactivationReason.RemoveWormholes ); planetLinksUpdated = true; } return DelReturn.Continue; } ); debugCode = 800; IWormholePlacer wormholePlacer = NomadPlacers[Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, NomadPlacers.Count )]; for ( int j = 0; j < PlanetsNearNomads.Count; j++ ) { debugCode = 900; //set the appropriate links Planet otherPlanet = PlanetsNearNomads[j]; if ( !planet.GetIsDirectlyLinkedTo( false, otherPlanet ) ) { debugCode = 1000; if ( otherPlanet.PopulationType == PlanetPopulationType.HumanHomeworld || otherPlanet.PopulationType == PlanetPopulationType.AIHomeworld ) //AIBastionWorld ignore wormholePlacer = HomeworldPlacer; LinkPlanetsAndAddWormholes( planet, otherPlanet, wormholePlacer, Engine_AIW2.Instance.MainThreadContext ); planetLinksUpdated = true; } } debugCode = 1200; if ( planetLinksUpdated ) { //note this doesn't update "Original Hops to Player/Human Homeworld"; unsure if that should be updated World_AIW2.Instance.CurrentGalaxy.RecomputeLinkedPathfindables(); } debugCode = 1300; World_AIW2.Instance.CurrentGalaxy.RecomputePlanetDistances(); if ( nomadFaction != null ) { //We are a nomad planet happily puttering along NomadPlanetsGlobalData gData = nomadFaction.GetNomadPlanetsGlobalDataExt( ExternalDataRetrieval.ReturnNullIfNotFound ); if ( planet.NomadTargetPlanetIdx != -1 ) //we are en route to crash { int interval = -1; //a default for when this code runs before the Nomad Sim code that sets the interval if ( !gData.MoveIntervalForCrash.ContainsKey(planet.Index) ) { if ( gData.TimeNomadCrashStarted > 0 ) { //the move interval is set at the same time the time nomad crash started throw new Exception("We don't know the move interval for this nomad planet " + planet.Index); } interval = 20; //a default; the Nomad Sim code will correct this later } else { //we are en route to our target; if we are already really close, don't move any more int totalDistance = Mat.DistanceBetweenPointsImprecise( planet.GalaxyLocation, targetPlanet.GalaxyLocation); if ( totalDistance < 40 ) interval = planet.SecondsTillNomadCrashes; else interval = gData.MoveIntervalForCrash[planet.Index]; //this is the normal path } planet.TimeForNextMove = World_AIW2.Instance.GameSecond + interval + Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, SpecialFaction_NomadPlanets.VarianceBetweenMoveTimesCrash ); } else if ( SpecialFaction_NomadPlanets.BaseMoveTime <= 0 ) //at game start time we haven't loaded the XML constants, so pick something pretty random { planet.TimeForNextMove = World_AIW2.Instance.GameSecond + 600 + Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, 200 * 8 ); } else //this is the normal case planet.TimeForNextMove = World_AIW2.Instance.GameSecond + SpecialFaction_NomadPlanets.BaseMoveTime + Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, SpecialFaction_NomadPlanets.VarianceBetweenMoveTimes ); } else { //we are a nomad planet w/o nomad planets enabled; this is probably because a Miner has Nomadified a planet planet.TimeForNextMove = World_AIW2.Instance.GameSecond + 600 + Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, 200 * 8 ); } World_AIW2.Instance.CurrentGalaxy.NomadHasMoved = true; //this gets set on both the Planet and Galaxy objects, for simplicity planet.NomadHasMoved = true; } } debugCode = 2000; if ( World_AIW2.Instance.CurrentGalaxy == null ) return; //this seems to be possible immediately after game load? if ( World_AIW2.Instance.CurrentGalaxy.NomadHasMoved ) { debugCode = 2100; bool reconnectionNecessary = ReconnectGalaxyIfNecessary( Context ); if ( reconnectionNecessary ) { World_AIW2.Instance.CurrentGalaxy.RecomputeLinkedPathfindables(); } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "Hit exception in UpdateNomadPlanets. debugCode " + debugCode + " " + e.ToString(), Verbosity.ShowAsError ); } } private bool ReconnectGalaxyIfNecessary ( ArcenSimContext Context ) { List unconnectedPlanets = new List(); int debugCode = 0; bool wasUnconnected = false; try{ ArcenDebugging.ArcenDebugLogSingleLine("Checking if fully connected ", Verbosity.DoNotShow ); while ( !World_AIW2.Instance.CurrentGalaxy.IsGalaxyFullyConnected( ref unconnectedPlanets ) ) { wasUnconnected = true; debugCode = 410; if ( unconnectedPlanets.Count == 0 ) throw new Exception( "The galaxy isn't fully connected, but there are no unconnected planets" ); Planet unlinkedPlanet = GetPlanetToLinkOrNull(unconnectedPlanets); if ( unlinkedPlanet == null ) break; //our only unlinked planets are DZ waiting to warp in ArcenDebugging.ArcenDebugLogSingleLine("Galaxy is not fully connected. First, reconnect " + unlinkedPlanet.Name, Verbosity.DoNotShow ); //link this planet to its nearest unlinked neighbor Planet nearestUnlinkedNeighbor = null; int distanceToNeighbor = -1; debugCode = 420; World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet potentialLink ) { debugCode = 430; if ( potentialLink.ToBeDestroyed || potentialLink.HasBeenDestroyed ) //the planet about to be destroyed is in the ToBeDestroyed state return DelReturn.Continue; if ( potentialLink.PopulationType == PlanetPopulationType.DarkZenith && potentialLink.GetControllingOrInfluencingFaction().Implementation as SpecialFaction_DarkZenith != null ) { //this is a DZ planet that warped in; make sure they are ready to link to the galaxy before linking them DarkZenithGlobalData dzData = potentialLink.GetControllingOrInfluencingFaction().GetDarkZenithGlobalDataExt( ExternalDataRetrieval.CreateIfNotFound ); if ( !dzData.HasLinkedPlanets ) continue; } if ( unlinkedPlanet == potentialLink || unlinkedPlanet.GetIsDirectlyLinkedTo( false, potentialLink ) || unconnectedPlanets.Contains( potentialLink ) ) return DelReturn.Continue; debugCode = 440; if ( nearestUnlinkedNeighbor == null || distanceToNeighbor > unlinkedPlanet.GetDistanceTo( potentialLink ) ) { nearestUnlinkedNeighbor = potentialLink; distanceToNeighbor = unlinkedPlanet.GetDistanceTo( nearestUnlinkedNeighbor ); return DelReturn.Continue; } return DelReturn.Continue; } ); ArcenDebugging.ArcenDebugLogSingleLine("Link and add wormholes between " + unlinkedPlanet.Name + " and " + nearestUnlinkedNeighbor.Name, Verbosity.DoNotShow ); debugCode = 500; IWormholePlacer placer = NomadPlacers[Engine_AIW2.Instance.MainThreadContext.RandomToUse.Next( 0, NomadPlacers.Count )]; LinkPlanetsAndAddWormholes( unlinkedPlanet, nearestUnlinkedNeighbor, placer, Engine_AIW2.Instance.MainThreadContext ); } }catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "Hit exception in ReconnectGalaxyIfNecessary debugCode " + debugCode + " " + e.ToString(), Verbosity.ShowAsError ); } return wasUnconnected; } private Planet GetPlanetToLinkOrNull( List planets ) { for ( int i = 0; i < planets.Count; i++ ) { Planet planet = planets[i]; if ( planet.PopulationType == PlanetPopulationType.DarkZenith && planet.GetControllingOrInfluencingFaction().Implementation as SpecialFaction_DarkZenith != null ) { //this is a DZ planet that warped in; make sure they are ready to link to the galaxy before linking them DarkZenithGlobalData dzData = planet.GetControllingOrInfluencingFaction().GetDarkZenithGlobalDataExt( ExternalDataRetrieval.CreateIfNotFound ); if ( !dzData.HasLinkedPlanets ) continue; } return planet; } return null; } private void DestroyPlanetsIfNecessary( ArcenSimContext Context ) { //This function removes planets from the galaxy (from nomads crashing, from miners, etc...) int debugCode = 0; try { debugCode = 100; bool anyPlanetsKilled = false; World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { debugCode = 200; if ( !planet.ToBeDestroyed ) return DelReturn.Continue; anyPlanetsKilled = true; debugCode = 210; planet.Network_HostOnly_NeedToSyncPlanetPositionAndLinksAndWormholesToClients = true; planet.DestroyPlanet( Context ); return DelReturn.Continue; } ); debugCode = 300; if ( anyPlanetsKilled ) { debugCode = 400; ArcenDebugging.ArcenDebugLogSingleLine("Some planets were killed, sim code", Verbosity.DoNotShow ); bool reconnectionNecessary = ReconnectGalaxyIfNecessary( Context ); debugCode = 600; if ( reconnectionNecessary ) { World_AIW2.Instance.CurrentGalaxy.NomadHasMoved = true; //a bit of a cheat; this is only set to force the newly visible planets World_AIW2.Instance.CurrentGalaxy.RecomputeLinkedPathfindables(); } } } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "Hit exception in DestroyPlanetsIfNecessary debugCode " + debugCode + " " + e.ToString(), Verbosity.ShowAsError ); } } private void LinkPlanetsAndAddWormholes(Planet planet, Planet otherPlanet, IWormholePlacer wormholePlacer, ArcenSimContext Context) { planet.AddLinkTo(otherPlanet); //also add wormhole objects int largerIndex = Math.Max( planet.Index, otherPlanet.Index ); int smallerIndex = Math.Min( planet.Index, otherPlanet.Index ); int seed = ( largerIndex << 16 ) + smallerIndex; GameEntityTypeData wormholeData = GameEntityTypeDataTable.Instance.GetRandomRowOfOtherSpecialType( Context, OtherSpecialEntityType.Wormhole, seed ); ArcenPoint wormholePoint = wormholePlacer.GetPointForWormhole( Context, planet, otherPlanet ); PlanetFaction pFaction = planet.GetFirstFactionOfType( FactionType.NaturalObject ); GameEntity_Other wormholeOrNull = GameEntity_Other.CreateNew_CallFromHostOnly( pFaction, wormholeData, wormholePoint, Context ); if ( wormholeOrNull != null ) wormholeOrNull.SetLinkedPlanetIndex( otherPlanet.Index ); planet.RecomputeDestinationIndexToWormholeMapping(); pFaction = otherPlanet.GetFirstFactionOfType( FactionType.NaturalObject ); ArcenPoint otherWormholePoint = wormholePlacer.GetPointForWormhole( Context, otherPlanet, planet ); GameEntity_Other otherWormholeOrNull = GameEntity_Other.CreateNew_CallFromHostOnly( pFaction, wormholeData, otherWormholePoint, Context ); if ( otherWormholeOrNull != null ) otherWormholeOrNull.SetLinkedPlanetIndex( planet.Index ); otherPlanet.RecomputeDestinationIndexToWormholeMapping(); planet.Network_HostOnly_NeedToSyncPlanetPositionAndLinksAndWormholesToClients = true; otherPlanet.Network_HostOnly_NeedToSyncPlanetPositionAndLinksAndWormholesToClients = true; } private ArcenPoint FindNextNomadPoint(Planet nomad, ArcenSimContext Context, int totalNomads) { ArcenPoint preferredLocation, output; Planet targetPlanet = World_AIW2.Instance.GetPlanetByIndex(nomad.NomadTargetPlanetIdx); ArcenPoint previousLocation = nomad.GalaxyLocation; AngleDegrees currentAngle = Engine_AIW2.Instance.GalaxyMapOnly_GalaxyCenter.GetAngleToDegrees( previousLocation ); int distance = Mat.DistanceBetweenPointsImprecise( nomad.GalaxyLocation, Engine_AIW2.Instance.GalaxyMapOnly_GalaxyCenter ); int preferredDistanceFromOtherPlanets = 40; int preferredDistanceFromPoint = 10; if ( targetPlanet != null ) { //go toward a target and try to crash ArcenPoint finalDestination = targetPlanet.GalaxyLocation; currentAngle = finalDestination.GetAngleToDegrees( previousLocation ); int totalDistance = Mat.DistanceBetweenPointsImprecise( previousLocation, finalDestination ); Faction nomadFaction = FactionUtilityMethods.GetNomadPlanetFaction(); int movementDistance = SpecialFaction_NomadPlanets.DistanceToMoveForCrash; //default value; will probably be overridden below if ( totalDistance < 40 ) movementDistance = 0; //we're already really close; just chill else if ( nomadFaction != null ) { NomadPlanetsGlobalData gData = nomadFaction.GetNomadPlanetsGlobalDataExt( ExternalDataRetrieval.ReturnNullIfNotFound ); if ( gData != null && gData.MoveIntervalForCrash.ContainsKey(nomad.Index) && nomad.SecondsTillNomadCrashes > 0 ) { int interval = gData.MoveIntervalForCrash[nomad.Index]; int numHopsLeft = nomad.SecondsTillNomadCrashes / interval; if (numHopsLeft <= 0 ) movementDistance = 0; else movementDistance = totalDistance / numHopsLeft; ArcenDebugging.ArcenDebugLogSingleLine("Moving a crashing planet; there are " + numHopsLeft + " hops left and we are moving " + movementDistance, Verbosity.DoNotShow ); } else movementDistance = 2; } preferredLocation = previousLocation.GetPointTowardsOther( finalDestination, movementDistance, totalDistance ); output = BadgerUtilityMethods.GetSafePointNearPoint( nomad, preferredLocation, World_AIW2.Instance.CurrentGalaxy, preferredDistanceFromOtherPlanets, preferredDistanceFromPoint, Engine_AIW2.Instance.MainThreadContext ); ArcenDebugging.ArcenDebugLogSingleLine("Crash Path: Moving nomad planet " + nomad.Name + " units " + movementDistance + " previous galaxy location was " +nomad.GalaxyLocation.ToString() + ", preferredLocation " + preferredLocation.ToString() + " but actually using " + output.ToString() + ". totalDistance to target " + totalDistance + " and target location " + targetPlanet.GalaxyLocation + ".", Verbosity.DoNotShow ); return output; } int attempts = 0; int distMoved = 0; AngleDegrees change; AngleDegrees newAngle; int minDistanceForMove = 30; //make sure we move at least a decent amount do { int angleChangePerMove = Context.RandomToUse.Next(2, 5); angleChangePerMove += attempts; if ( totalNomads < 3 ) angleChangePerMove += 3; else if ( totalNomads < 5 ) angleChangePerMove += 2; if ( nomad.Index % 2 == 0 ) //even and odd nomads move opposite directions angleChangePerMove *= -1; change = AngleDegrees.Create(angleChangePerMove); newAngle = currentAngle.Add(change); //if we were at 180 degrees from galaxy center, go to 180 + angleChangePerMove int origDistance = nomad.OriginalNomadDistance; //TODO: If we have a rectangular map and wind up too far away from other planets it looks weird. I'm including the original distance we were in case we want to change the distance //when on the short side of the rectangle. //int ActualDistanceToMove = 30; preferredLocation = Mat.GetPointFromCircleCenter(Engine_AIW2.Instance.GalaxyMapOnly_GalaxyCenter , distance, newAngle); distMoved = Mat.DistanceBetweenPointsImprecise(previousLocation, preferredLocation); attempts++; }while (distMoved < minDistanceForMove && attempts < 20 ); output = BadgerUtilityMethods.GetSafePointNearPoint( nomad, preferredLocation, World_AIW2.Instance.CurrentGalaxy, preferredDistanceFromOtherPlanets, preferredDistanceFromPoint, Engine_AIW2.Instance.MainThreadContext ); ArcenDebugging.ArcenDebugLogSingleLine("Moving nomad planet " + nomad.Name + " previous galaxy location was " +nomad.GalaxyLocation.ToString() + " and new location is " + output.ToString() + ". old angle: " + currentAngle + " new angle: " + newAngle + " angle change " + change + " distance moved " + distMoved + " attempts " + attempts, Verbosity.DoNotShow ); return output; } #endregion #region DoWorldSecondLogic_FromSimBGThread public void DoWorldSecondLogic_FromSimBGThread( ArcenSimContext Context ) { World_AIW2.Instance.GameSecond++; StatisticSet.RecordSecondPlayed( 1 ); //int secondsPerAIPIncrease = this.Setup.MinutesPerAIPIncrease * 60; //if ( secondsPerAIPIncrease > 0 && this.GameSecond % secondsPerAIPIncrease == 0 ) // this.ChangeAIP( (FInt)this.Setup.AIPPerAIPIncrease, AIPChangeReason.AutoIncrease, null, Context ); bool simStageDebugLog = GameSettings_AIW2.Current.GetBool( ArcenBoolSetting_AIW2.PerSecondSimStageDebugLog ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START WorldSecondLogic", Verbosity.DoNotShow ); Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "WorldSecondLogic_Factions" ); for ( int i = 0; i < SpecialFactionDataTable.Instance.Rows.Count; i++ ) { SpecialFactionData typeData = SpecialFactionDataTable.Instance.Rows[i]; if ( typeData == null ) continue; if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START Stage0 " + typeData.InternalName, Verbosity.DoNotShow ); typeData.Safe_DoPerSecondLogic_Stage0Clearing_OnMainThreadAndPartOfSim_OncePerFactionTypeEvenForFactionsNotInGame( Context ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH Stage0 " + typeData.InternalName, Verbosity.DoNotShow ); } //Engine_Universal.BeginProfilerSample( "faction-second" ); World_AIW2.Instance.DoForFactions( delegate ( Faction faction ) { faction.PerSecondFactionStopWatch.Reset(); faction.PerSecondFactionStopWatch.Start(); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START SpeedGroup And Stage1 " + faction.GetDisplayName(), Verbosity.DoNotShow ); faction.DoSpeedGroupLogic_HostOnly(); faction.Safe_DoPerSecondLogic_Stage1Clearing_OnMainThreadAndPartOfSim( Context ); faction.PerSecondFactionStopWatch.Stop(); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH SpeedGroup And Stage1 " + faction.GetDisplayName(), Verbosity.DoNotShow ); return DelReturn.Continue; } ); World_AIW2.Instance.DoForFactions( delegate ( Faction faction ) { if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START PerSecond And Stage2 " + faction.GetDisplayName(), Verbosity.DoNotShow ); faction.PerSecondFactionStopWatch.Start(); faction.DoPerSecondSimUpdate( Context ); faction.Safe_DoPerSecondLogic_Stage2Aggregating_OnMainThreadAndPartOfSim( Context ); try { faction.Implementation.UpdatePowerLevel( faction ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "UpdatePowerLevel error in faction: " + faction.GetDisplayName() + "\n" + e, Verbosity.DoNotShow ); } try { faction.Implementation.CheckIfPlayerHasSeenFaction( faction, Context ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "CheckIfPlayerHasSeenFaction error in faction: " + faction.GetDisplayName() + "\n" + e, Verbosity.DoNotShow ); } try { faction.Implementation.UpdatePlanetInfluence( faction, Context ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "UpdatePlanetInfluence error in faction: " + faction.GetDisplayName() + "\n" + e, Verbosity.DoNotShow ); } try { faction.Implementation.UpdateInvasionTime( faction, Context ); } catch ( Exception e ) { ArcenDebugging.ArcenDebugLogSingleLine( "CheckIfPlayerHasSeenFaction error in faction: " + faction.GetDisplayName() + "\n" + e, Verbosity.DoNotShow ); } faction.PerSecondFactionStopWatch.Stop(); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH PerSecond And Stage2 " + faction.GetDisplayName(), Verbosity.DoNotShow ); return DelReturn.Continue; } ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START Planet Influence Calculations", Verbosity.DoNotShow ); //Now that we've updated all the planetary influences, sort them from "strongest force on planet" //to "weakest force World_AIW2.Instance.DoForPlanetsParallel( false, delegate ( Planet planet ) { if ( planet.UnderInfluenceOfFactionIndex.Count == 0 ) { planet.PrimaryInfluencingFaction = -1; return DelReturn.Continue; } planet.UnderInfluenceOfFactionIndex.Sort( delegate (short L, short R) { //get the strength of faction index L and R, then sort Faction lFaction = World_AIW2.Instance.GetFactionByIndex(L); Faction rFaction = World_AIW2.Instance.GetFactionByIndex(R); int lStrength = planet.GetPlanetFactionForFaction(lFaction).DataByStance[FactionStance.Self].TotalStrength; int rStrength = planet.GetPlanetFactionForFaction(rFaction).DataByStance[FactionStance.Self].TotalStrength; return lStrength.CompareTo(rStrength); } ); planet.PrimaryInfluencingFaction = planet.UnderInfluenceOfFactionIndex[0]; return DelReturn.Continue; } ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH Planet Influence Calculations", Verbosity.DoNotShow ); World_AIW2.Instance.DoForFactions( delegate ( Faction faction ) { faction.PerSecondFactionStopWatch.Start(); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START Stage3 " + faction.GetDisplayName(), Verbosity.DoNotShow ); faction.Safe_DoPerSecondLogic_Stage3Main_OnMainThreadAndPartOfSim( Context ); FactionPerSecondBonusGeneralMarkLevelsCheck( faction, Context ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH Stage3 " + faction.GetDisplayName(), Verbosity.DoNotShow ); faction.PerSecondFactionStopWatch.Stop(); faction.PerSecondFactionMSLastFrame = (int)faction.PerSecondFactionStopWatch.ElapsedMilliseconds; return DelReturn.Continue; } ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "WorldSecondLogic_Factions" ); //Engine_Universal.EndProfilerSample( "faction-second" ); Engine_Universal.NewTimingsBeingBuilt.StartRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "WorldSecondLogic_Planets" ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START Planet View Calculations", Verbosity.DoNotShow ); int localPlanetID = PlayerAccount_AIW2.GetViewingPlanetIndexSafe(); //Engine_Universal.BeginProfilerSample( "planet-second" ); World_AIW2.Instance.DoForPlanetsSingleThread( false, delegate ( Planet planet ) { EntitySimLogic.Instance.DoCombatSecond_FromSimBGThread( planet, Context ); //fix old savegames if they are off if ( planet.Index == localPlanetID && planet.ViewedByPlayerAccounts_DuringGame.Count == 0 ) { GameCommand command = GameCommand.Create( GameCommandTypeTable.CoreFunctions[CoreFunction.SetPlanetViewedByPlayerAccount], GameCommandSource.AnythingElse ); command.RelatedIntegers.Add( PlayerAccount.Local.PlayerPrimaryKeyID ); command.RelatedIntegers2.Add( planet.Index ); World_AIW2.Instance.QueueGameCommand( command, false ); } return DelReturn.Continue; } ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH Planet View Calculations", Verbosity.DoNotShow ); Engine_Universal.NewTimingsBeingBuilt.FinishRememberingFrame( FramePartTimings.TimingType.BackgroundSimThreadSubItem, "WorldSecondLogic_Planets" ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START Fleet PerSecond Calculations", Verbosity.DoNotShow ); //Engine_Universal.EndProfilerSample( "planet-second" ); for ( int i = 0; i < World_AIW2.Instance.Fleets.Count; i++ ) World_AIW2.Instance.Fleets[i].PerSecond_UpdateFleetData( Context ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH Fleet PerSecond Calculations", Verbosity.DoNotShow ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "START Hacking Difficulty Estimate Calculations", Verbosity.DoNotShow ); if ( !HackingTypeTable.Instance.DifficultyEstimationDone || (HackingTypeTable.Instance.DifficultyEstimationDone && World_AIW2.Instance.GameSecond % 360 == 0) ) { //I'm not sure if this is the optimal place to do this sort of thing. It should be done once at game load //time, and then sporadically at other times HackingTypeTable.Instance.UpdateDifficultyEstimates(); } if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH Hacking Difficulty Estimate Calculations", Verbosity.DoNotShow ); if ( simStageDebugLog ) ArcenDebugging.ArcenDebugLogSingleLine( "FINISH WorldSecondLogic", Verbosity.DoNotShow ); } #endregion private static void FactionPerSecondBonusGeneralMarkLevelsCheck( Faction Fac, ArcenSimContext Context ) { if ( World_AIW2.Instance.GetIsTutorial() ) return; //don't add any mark levels to ships in the tutorial. byte addedLevels = 0; if ( Fac.SpecialFactionData.GetsPraetorianBonusToMarkLevelFromAIFactionDifficulty ) { Faction relatedAIFaction = Fac.GetPrecedingAIFaction(); if ( relatedAIFaction != null ) { AISentinelsExternalData sentinelsExt = relatedAIFaction.GetSentinelsExternal( ExternalDataRetrieval.ReturnNullIfNotFound ); if ( sentinelsExt != null ) { AIDifficulty difficulty = sentinelsExt.AIDifficulty; if ( difficulty != null ) { addedLevels = difficulty.AddedPraetorianMarkLevelsAboveAmbient; } } } } Fac.CurrentGeneralMarkLevel_Added = addedLevels; } } }