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;
}
}
}