using Arcen.Universal;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Arcen.AIW2.Core;
using System.Linq;
namespace Arcen.AIW2.External
{
///
/// All of the things here are run on a secondary thread,
/// from in the World_AIW2.Instance.DoWorldStepLogic tree,
/// in the last secondary-thread step of the sim before SimPlannerImplementation
/// (on the main thread) can flip to the next sim-frame.
///
/// This gets run once per entity.
///
public class EntitySimLogicImplementation : EntitySimLogic
{
public EntitySimLogicImplementation()
{
EntitySimLogic.Instance = this;
}
private bool _trace;
private readonly ArcenCharacterBuffer traceBuffer = new ArcenCharacterBuffer();
#region ReevaluateUnitOrders
public override void ReevaluateUnitOrders( ArcenSimContext Context, GameEntity_Squad Entity )
{
if ( World.Instance.IsPaused )
return; //don't waste my time, buddy!
if ( Entity.Planet != null && Entity.Planet.BattleStatus == PlanetBattleStatus.Tier1_PlayerLookingAtMe )
{
//only run the sim cycle groups on the main planet; otherwise we may miss sim cycles entirely
if ( World_AIW2.CurrentSimCycleSlow != Entity.SimCycleGroup_Slow )
return; //catch you next time!
}
_trace = Engine_AIW2.TraceAtAll && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && Entity == GameEntity_Base.CurrentlyHoveredOver;
if ( _trace ) traceBuffer.Clear();
if ( _trace ) traceBuffer.Add( "ReevaluateUnitOrders: " ).Add( Entity.TypeData.InternalName ).Add( " " ).Add( Entity.PrimaryKeyID );
GameEntity_Squad guarded = Entity.Guarding.GetSquad();
if ( guarded != null && guarded.PlanetFaction.Faction.Type != Entity.PlanetFaction.Faction.Type )
{
guarded = null;
Entity.GuardingOffsets.Clear();
Entity.Guarding.PrimaryKeyID = 0;
Entity.Guarding.Ref = null;
}
EntityOrder order = Entity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision();
EntityBehaviorType effectiveBehavior = Entity.GetEffectiveOrders().Behavior;
if ( effectiveBehavior != EntityBehaviorType.Attacker_PursueOnlyInRange )
{
if ( order != null && order.ShouldOverrideBehavior )
{
if ( _trace )
{
if(order.TypeData.Type == EntityOrderType.Wormhole)
{
traceBuffer.Add("Obeying order type " + order.TypeData.Type + " to " + World_AIW2.Instance.GetPlanetByIndex(order.RelatedPlanetIndex).Name + " which overrides the current behaviour");
}
else
traceBuffer.Add("Obeying order type " + order.TypeData.Type + " which overrides the current behaviour");
ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow );
traceBuffer.Clear();
}
return;
}
//we rely on this to block us from accidentally ordering around things that are already given orders by humans.
//that way we can use ClearSource.YesClearHumanOrders, below
if ( order != null && order.Source == OrderSource.HumanPlayer )
{
if ( _trace )
{
traceBuffer.Add("Obeying order from player which overrides behaviour");
ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow );
traceBuffer.Clear();
}
return;
}
}
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Entity.GetEffectiveOrders().Behavior=" ).Add( effectiveBehavior.ToString() );
switch ( effectiveBehavior )
{
case EntityBehaviorType.Guard_FleetShip:
case EntityBehaviorType.Guard_Guardian_Patrolling:
case EntityBehaviorType.Guard_Guardian_Anchored:
{
bool isFreeingFromAggro = false;
GameEntity_Squad guardSquad = Entity.Guarding.GetSquad();
if ( guardSquad != null )
{
if ( guardSquad.LastTimeTakenDamageFromPlayer > 0 )
{
//aggro me because guard was aggro'd
if ( Entity.LastTimeTakenDamageFromPlayer < guardSquad.LastTimeTakenDamageFromPlayer )
Entity.LastTimeTakenDamageFromPlayer = guardSquad.LastTimeTakenDamageFromPlayer;
isFreeingFromAggro = true;
}
else if ( Entity.LastTimeTakenDamageFromPlayer > 0 )
{
//aggro guard because I was aggro'd
if ( Entity.LastTimeTakenDamageFromPlayer > guardSquad.LastTimeTakenDamageFromPlayer )
guardSquad.LastTimeTakenDamageFromPlayer = Entity.LastTimeTakenDamageFromPlayer;
isFreeingFromAggro = true;
}
}
else if ( Entity.LastTimeTakenDamageFromPlayer > 0 )
isFreeingFromAggro = true;
if ( isFreeingFromAggro )
{
Entity.Orders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
Entity.GuardingOffsets.Clear();
Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, -1 );
effectiveBehavior = EntityBehaviorType.Attacker_Full;
}
}
break;
}
switch ( effectiveBehavior )
{
case EntityBehaviorType.Guard_FleetShip:
if ( Entity.TypeData.CannotBeStoredInsideGuardPost )
{
effectiveBehavior = EntityBehaviorType.Attacker_Full;
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because CannotBeStoredInsideGuardPost" );
}
else
{
if ( Entity.PlanetFaction.Faction.Type == FactionType.Player )
{
if ( Entity.PlanetFaction.DataByStance[FactionStance.Hostile].TotalStrengthIncludingNonMilitary > 0 )
{
effectiveBehavior = EntityBehaviorType.Attacker_Full;
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because player-side and Hostile is present" );
}
}
else
{
ShortRangePlanning_StrengthData_PlanetFaction_Stance hostileData = Entity.PlanetFaction.DataByStance[FactionStance.Hostile];
if ( hostileData.SecondsSinceHadAnyStrength >= 0 &&
hostileData.SecondsSinceHadAnyStrength < ExternalConstants.Instance.ResidualAlertSeconds )
{
effectiveBehavior = EntityBehaviorType.Attacker_Full;
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "acting as Attacker because non-player-side and player is present (" ).Add( hostileData.SecondsSinceHadAnyStrength ).Add( " seconds ago, residual alert for " ).Add( ExternalConstants.Instance.ResidualAlertSeconds ).Add( " seconds)" );
}
}
}
break;
case EntityBehaviorType.Guard_Guardian_Patrolling:
{
ShortRangePlanning_StrengthData_PlanetFaction_Stance hostileData = Entity.PlanetFaction.DataByStance[FactionStance.Hostile];
int secondsSinceLastHostilePresence = hostileData.SecondsSinceHadAnyStrength;
if ( secondsSinceLastHostilePresence >= 0 &&
secondsSinceLastHostilePresence < ExternalConstants.Instance.ResidualAlertSeconds )
{
bool isFreeing = false;
int freeingAainstFactionIndex = -1;
if ( Entity.HullPointsLost > 0 || Entity.ShieldPointsLost > 0 )
isFreeing = true;
else
{
// if we need a more sophisticated "am I near any enemies" check than Entity, (for instance, better limiting on the range), then we should probably find some way to offload the bulk of Entity to a planning thread
DelegateHelper_CheckForGuardFreeingProvocation_guardPoint = Entity.WorldLocation;
DelegateHelper_CheckForGuardFreeingProvocation_foundHit = null;
DelegateHelper_GuardAggroDistance = AIWar2GalaxySettingTable.GetIsIntValueFromSettingByName_DuringGame( "GuardAggroDistance" );
for ( int i = 0; i < Entity.Planet.Factions.Count; i++ )
{
PlanetFaction faction = Entity.Planet.Factions[i];
if ( !Entity.PlanetFaction.GetIsHostileTowards( faction ) )
continue;
faction.Entities.DoForEntities( DelegateHelper_CheckForGuardFreeingProvocation );
if ( DelegateHelper_CheckForGuardFreeingProvocation_foundHit != null )
{
isFreeing = true;
freeingAainstFactionIndex = DelegateHelper_CheckForGuardFreeingProvocation_foundHit.PlanetFaction.Faction.FactionIndex;
break;
}
}
DelegateHelper_CheckForGuardFreeingProvocation_foundHit = null;
}
if ( isFreeing )
{
effectiveBehavior = EntityBehaviorType.Attacker_Full;
//if ( hostileData.MobileStrength > FInt.Zero )
{
Entity.Orders.ClearOrders( ClearBehavior.AnythingIncludingAttackerMode, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
Entity.GuardingOffsets.Clear();
Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, freeingAainstFactionIndex );
}
}
}
}
break;
}
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "effectiveBehavior=" ).Add( effectiveBehavior.ToString() );
switch ( effectiveBehavior )
{
case EntityBehaviorType.Attacker_Full:
this.AttackerLogic( Context, Entity, order, guarded, false );
break;
case EntityBehaviorType.Attacker_PursueOnlyInRange:
this.AttackerLogic( Context, Entity, order, guarded, true );
break;
//case EntityBehaviorType.RallyToPlayerFleet:
// this.FleetRallyLogic( Context, Entity, order );
// break;
case EntityBehaviorType.Guard_FleetShip:
this.Guard_FleetShipLogic( Context, Entity, order, guarded );
break;
case EntityBehaviorType.Guard_Guardian_Patrolling:
this.Guard_Guardian_PatrollingLogic( Context, Entity, order, guarded );
break;
case EntityBehaviorType.Guard_Guardian_Anchored:
this.Guard_Guardian_AnchoredLogic( Context, Entity, order, guarded );
break;
}
if ( _trace )
{
ArcenDebugging.ArcenDebugLogSingleLine( traceBuffer.ToString(), Verbosity.DoNotShow );
traceBuffer.Clear();
}
}
#endregion
#region AttackerLogic
private void AttackerLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool AllowOverridingHumanOrders )
{
if ( guarded != null && Entity.GetMatches( EntityRollupType.ReinforcementLocations ) )
{
// if we're a guardian, guarding something, but we're in the Attacker mode, then we should cut loose the rest of the way and become real threat
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
guarded = null;
Entity.GuardingOffsets.Clear();
Entity.Guarding.PrimaryKeyID = 0;
Entity.Guarding.Ref = null;
return; // next time around we'll revisit the question of what really to do
}
if ( Entity.GetMatches(EntityRollupType.MobileFleetFlagships) && !Entity.FleetMembership.Fleet.IsFleetFlagshipAllowedToUseMovementModes )
return; //flagships are only allowed to use this logic if the user has requested it
if ( Entity.GetMatches( EntityRollupType.HasAnyMetalFlows ) &&
Entity.DataForMark.AssistRange > 0 &&
(!Entity.TypeData.IsCombatant || Entity.TypeData.IsCombatantDespiteNoWeapons ) )
{
this.AttackerLogic_Assister( Context, Entity, order, guarded );
}
else
{
this.AttackerLogic_Combat( Context, Entity, order, guarded, AllowOverridingHumanOrders );
}
}
#endregion
#region AttackerLogic_Assister
private void AttackerLogic_Assister( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
{
bool needNewOrder = order == null || order.TypeData.Type != EntityOrderType.Move_Normal;
//we rely on this to block us from accidentally ordering around things that are already given orders by humans.
//that way we can use ClearSource.YesClearHumanOrders, below
if ( order != null && order.Source == OrderSource.HumanPlayer )
return;
if ( !needNewOrder )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, already moving" );
return;
}
int allowedDistance = ( Entity.DataForMark.AssistRange * 9 ) / 10;
GameEntity_Squad bestTarget = null;
int bestDistance = 0;
for ( int i = 0; i < Entity.FramePlan_PlannedFlows.Count; i++ )
{
PlannedMetalFlow flow = Entity.FramePlan_PlannedFlows[i];
switch ( flow.Purpose )
{
case MetalFlowPurpose.ClaimingNeutrals:
case MetalFlowPurpose.RepairingHullsOfFriendlies:
case MetalFlowPurpose.RepairingShieldsOfFriendlies:
case MetalFlowPurpose.RepairingEnginesOfFriendlies:
case MetalFlowPurpose.RebuildingRemains:
case MetalFlowPurpose.AssistConstruction:
break;
default:
continue;
}
if ( flow.SquadRecipients.Count <= 0 )
continue;
GameEntity_Squad target = flow.SquadRecipients[0];
int distance;
if ( Entity.GetIsWithinRangeOf( target, allowedDistance, out distance ) )
{
needNewOrder = false;
break;
}
if ( bestTarget != null && bestDistance <= distance )
continue;
bestTarget = target;
bestDistance = distance;
}
if ( !needNewOrder )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, already in range of an assist target" );
return;
}
ArcenPoint targetPoint;
if ( bestTarget == null )
{
if ( Entity.GuardingOffsets.Count <= 0 )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, no eligible assist targets and no remembered guard point" );
return;
}
targetPoint = Entity.GuardingOffsets[0];
if ( Entity.GetIsWithinRangeOf( targetPoint, allowedDistance ) )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed, no eligible assist targets and within range of remembered guard point" );
return;
}
}
else
{
AngleDegrees angleToTarget = Entity.WorldLocation.GetAngleToDegrees( bestTarget.WorldLocation );
int closeToWithinRange = ( Entity.DataForMark.AssistRange * 8 ) / 10;
targetPoint = Entity.WorldLocation.GetPointAtAngleAndDistance( angleToTarget, closeToWithinRange );
}
if ( targetPoint == ArcenPoint.ZeroZeroPoint )
return; //this would be an invalid order
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other );
Entity.Orders.QueueOrder( Entity, newOrder );
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "picked assist target" ).Add( bestTarget.TypeData.InternalName );
}
#endregion
#region AttackerLogic_Combat
private void AttackerLogic_Combat( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded, bool AllowOverridingHumanOrders )
{
bool needNewOrder = order == null || order.TypeData.Type != EntityOrderType.Attack;
bool insertOrderInsteadOfClear = false;
//we rely on this to block us from accidentally ordering around things that are already given orders by humans.
//that way we can use ClearSource.YesClearHumanOrders, below
if ( !AllowOverridingHumanOrders )
{
if ( order != null && order.Source == OrderSource.HumanPlayer )
return;
}
else
{
insertOrderInsteadOfClear = true;
if ( order != null && order.Source == OrderSource.HumanPlayer )
{
//can't override these
switch ( order.TypeData.Type )
{
case EntityOrderType.Wormhole:
case EntityOrderType.Attack:
return;
default:
break;
}
}
}
//Entity.DebugText = DateTime.Now + " needNewOrder: " + needNewOrder;
#region If we have an existing order, think about changing it based on the FRD stuf
if ( !needNewOrder && !insertOrderInsteadOfClear )
{
GameEntity_Squad existingTarget = order.RelatedSquad.GetSquad();
if ( existingTarget == null || existingTarget.CalculateShouldBeClearedFromTargetingOfAttacker( Entity ) )
{
//this existing target is invalid! Get rid of that order
needNewOrder = true;
Entity.Orders.QueuedOrders.Remove( order );
order.ReturnToPool( Entity );
order = null;
}
else
{
//Entity.DebugText += " existingTarget: " + existingTarget.TypeData.InternalName;
}
}
if ( !needNewOrder )
{
if ( order.Source == OrderSource.HumanPlayer )
{
//if it was given by the player specifically, then don't override it!
}
else
{
bool foundAMatch = false;
EntitySystem system;
for ( int i = 0; i < Entity.Systems.Count; i++ )
{
system = Entity.Systems[i];
if ( system.CurrentFRDTarget.PrimaryKeyID == order.RelatedSquad.PrimaryKeyID )
foundAMatch = true;
}
//none of our systems are after the current FRD target anymore!
if ( !foundAMatch )
needNewOrder = true;
}
}
#endregion
if ( !needNewOrder )
{
//Entity.DebugText += " finalNotNeedNewOrder";
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no new order needed" );
return;
}
//since we might have multiple systems that want to do FRD chasing, choose the best one from the list
GameEntity_Squad bestTarget = null;
GameEntity_Squad workingTarget = null;
int bestTargetPriority = 0;
{
EntitySystem system;
for ( int i = 0; i < Entity.Systems.Count; i++ )
{
system = Entity.Systems[i];
if ( system.CurrentFRDTarget.PrimaryKeyID > 0 )
{
if ( Entity.DataForMark.HasAnySniperRangedWeapons )
{
if ( !system.TypeData.FiresFromAnyRange && system.DataForMark.BaseRange < 999999 )
continue; //if the ship has any sniper-ranged systems, then ONLY those systems can do FRD targeting
}
workingTarget = system.CurrentFRDTarget.GetSquad();
if ( workingTarget == null || workingTarget.GetHasBeenDestroyed() || workingTarget.HasBeenRemovedFromSim ||
workingTarget.SecondsSpentAsRemains > 0 || workingTarget.ToBeRemovedAtEndOfThisFrame )
{
//target not valid! Stop tracking it now
system.CurrentFRDTarget.SetInternalRef( null );
system.CurrentFRDPriority = 0;
system.TimeFRDTargetExpires = 0;
}
else
{
if ( system.CurrentFRDPriority > bestTargetPriority || bestTarget == null )
{
bestTarget = workingTarget;
bestTargetPriority = system.CurrentFRDPriority;
}
}
}
}
}
if ( bestTarget == null )
{
//Entity.DebugText += " bestTarget null!";
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no bestTarget found!" );
return;
}
//Entity.DebugText += " bestTarget: " + bestTarget.TypeData.InternalName;
if ( !insertOrderInsteadOfClear )
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.YesClearAnyOrders_IncludingFromHumans );
EntityOrder newOrder = EntityOrder.Create_Attack( Entity, bestTarget.PrimaryKeyID, false, OrderSource.Other );
newOrder.CalculateStrengthCountingData( Entity, true, true, true );
if ( insertOrderInsteadOfClear )
Entity.Orders.InsertOrderAtStart( Entity, newOrder );
else
Entity.Orders.QueueOrder( Entity, newOrder );
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "picked target" ).Add( bestTarget.TypeData.InternalName );
}
#endregion
#region FleetRallyLogic
//private void FleetRallyLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order )
//{
// if ( Entity.FleetMembership.Fleet.Centerpiece.PrimaryKeyID == Entity.PrimaryKeyID )
// {
// Entity.Orders.ClearOrders( ClearBehavior.RallyOrdersOnly, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
// Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "would be rallying to self since I am centerpiece, cancelling rally" );
// return;
// }
// //Entity only happens with new ships, so skipping checking against itself is good.
// GameEntity_Squad entityToRallyTo = Entity.FleetMembership.Fleet.Centerpiece.GetSquad();
// if ( entityToRallyTo == null )
// {
// Entity.Orders.ClearOrders( ClearBehavior.RallyOrdersOnly, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
// Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "no centerpiece in fleet, cancelling rally" );
// return;
// }
// if ( entityToRallyTo.Planet != Entity.Planet )
// {
// //the rally leader is on a different planet
// bool alreadyHaveWormholeOrderPath = false;
// for ( int i = 0; i < Entity.Orders.QueuedOrders.Count; i++ )
// {
// EntityOrder queuedOrder = Entity.Orders.QueuedOrders[i];
// if ( queuedOrder.TypeData.Type != EntityOrderType.Wormhole )
// break;
// if ( queuedOrder.RelatedPlanetIndex == entityToRallyTo.Planet.PlanetIndex )
// {
// alreadyHaveWormholeOrderPath = true;
// break;
// }
// }
// if ( alreadyHaveWormholeOrderPath )
// {
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "not on same planet, but moving to that planet. Carry On." );
// }
// else
// {
// List path = World_AIW2.Instance.GetLocalPlayerFaction().FindPath( Entity.Planet, entityToRallyTo.Planet, Context );
// if ( path.Count <= 0 )
// {
// Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
// Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "not on same planet, and cannot find path to that planet, cancelling rally" );
// }
// else
// {
// Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
// for ( int i = 0; i < path.Count; i++ )
// {
// Planet pathPlanet = path[i];
// EntityOrder newOrder = EntityOrder.Create_Wormhole( Entity, pathPlanet.PlanetIndex, false, false, OrderSource.Other );
// Entity.Orders.QueueOrder( Entity, newOrder );
// }
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "not on same planet, so giving wormhole path to that planet of " + Entity.Orders.QueuedOrders.Count + " hops" );
// }
// }
// }
// else
// {
// int distance = Entity.GetDistanceTo_VeryCheapButExtremelyRough( entityToRallyTo, false );
// int threshold = ( Entity.DataForMark.Radius + Entity.DataForMark.Radius + entityToRallyTo.DataForMark.Radius );
// if ( distance <= threshold )
// {
// Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
// Entity.Orders.SetBehavior( EntityBehaviorType.None, -1 );
// if ( entityToRallyTo.GetEffectiveOrders().Behavior != EntityBehaviorType.RallyToPlayerFleet )
// {
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "in range of the primary entity in control group " + entityToRallyTo.TypeData.InternalName + " " + entityToRallyTo.PrimaryKeyID + ", cancelling rally and copying its orders to me" );
// entityToRallyTo.Orders.CopyTo( Entity, Entity.Orders );
// }
// else
// //Entity condition can be hit if every unit in the control group died except the reinforcements being rallied to the original control group
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "in range of the primary entity in control group " + entityToRallyTo.TypeData.InternalName + " " + entityToRallyTo.PrimaryKeyID + ", but that unit is also control-group-rallying, so don't copy orders since otherwise we go in a loop" );
// }
// else
// {
// if ( order != null && order.TypeData.Type == EntityOrderType.Move && entityToRallyTo.GetDistanceTo_VeryCheapButExtremelyRough( order.RelatedPoint, false ) <= threshold )
// {
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "on same planet, out of range, but moving to a point that is in range. Carry On." );
// }
// else
// {
// Entity.Orders.ClearOrders( ClearBehavior.AnythingOtherThanRallyOrders, ClearDecollisionOnParent.YesClear, ClearSource.DoNotClearHumanOrders );
// EntityOrder newOrder = EntityOrder.Create_Move( Entity, entityToRallyTo.WorldLocation, false, OrderSource.Other );
// Entity.Orders.QueueOrder( Entity, newOrder );
// if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "on same planet, out of range, so giving move order to the rally leader's position." );
// }
// }
// }
//}
#endregion
#region Guard_FleetShipLogic
private void Guard_FleetShipLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
{
if ( guarded == null ||
guarded.ToBeRemovedAtEndOfThisFrame ||
guarded.HasBeenRemovedFromSim ||
guarded.Planet != Entity.Planet )
{
if ( !Entity.TypeData.CannotBeStoredInsideGuardPost && Entity.GetMatches( EntityRollupType.Drone ) )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: dying because my guarded-object is gone and I'm a drone" );
Entity.Die( Context, true );
}
else
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: clearing orders and going Attacker because my guarded-object is gone" );
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
int bestFactionIndex = Entity.PlanetFaction.GetIndexOfMostAnnoyingFaction( Context, true );
Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, bestFactionIndex );
}
}
else
{
if ( Entity.GetIsWithinRangeOf_VeryBasicCheckOnly( guarded.WorldLocation, ExternalConstants.Instance.GuardReabsorptionDistance ) )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: absorbing because I'm close enough to my guarded-object" );
guarded.AddToAIReinforcementPointContents( Entity.TypeData, 1 + Entity.ExtraStackedSquadsInThis );
Entity.ExtraStackedSquadsInThis = 0; //without this, it will just remove one from the stack and exponentially get more because of it!
Entity.ToBeRemovedAtEndOfThisFrame = true;
}
else
{
bool needNewOrder = false;
if ( order == null )
{
if ( _trace )
{
traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: need new order because no current order" )
.Add( " (currently " ).Add( Entity.GetDistanceTo_VeryCheapButExtremelyRough( guarded, false ) ).Add( " distance from guarded object " )
.Add( guarded.TypeData.InternalName ).Add( " #" ).Add( guarded.PrimaryKeyID )
.Add( ")" );
}
needNewOrder = true;
}
else if ( order.TypeData.Type != EntityOrderType.Move_Normal )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: need new order because current order is " ).Add( order.TypeData.Type.ToString() ).Add( " instead of Move" );
}
else if ( !guarded.GetIsWithinRangeOf_VeryBasicCheckOnly( order.RelatedPoint, ExternalConstants.Instance.GuardReabsorptionDistance ) )
{
if ( _trace )
traceBuffer.Add( Environment.NewLine )
.Add( "Guard_FleetShip: need new order because current order point (to " )
.Add( order.RelatedPoint.ToString() )
.Add( ") is too far from my guarded-object (" )
.Add( guarded.WorldLocation.ToString() )
.Add( "); my current location is (" )
.Add( Entity.WorldLocation.ToString() )
.Add( ")" )
;
needNewOrder = true;
}
if ( needNewOrder )
{
if ( _trace ) traceBuffer.Add( Environment.NewLine ).Add( "Guard_FleetShip: clearing orders and issuing new move order to get closer to my guarded-object" );
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, guarded.WorldLocation, false, OrderSource.Other );
Entity.Orders.QueueOrder( Entity, newOrder );
}
else
{
if ( _trace )
traceBuffer.Add( Environment.NewLine )
.Add( "Guard_FleetShip: doing nothing because I already have a move order (to " )
.Add( order.RelatedPoint.ToString() )
.Add( ") which is close enough to my guarded-object (" )
.Add( guarded.WorldLocation.ToString() )
.Add( "); my current location is (" )
.Add( Entity.WorldLocation.ToString() )
.Add( ")" )
;
}
}
}
}
#endregion
#region Guard_Guardian_PatrollingLogic
private void Guard_Guardian_PatrollingLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
{
if ( guarded == null || Entity.GuardingOffsets.Count <= 0 )
{
guarded = null;
Entity.GuardingOffsets.Clear();
Entity.Guarding.PrimaryKeyID = 0;
Entity.Guarding.Ref = null;
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
int bestFactionIndex = Entity.PlanetFaction.GetIndexOfMostAnnoyingFaction( Context, true );
Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, bestFactionIndex );
}
else
{
bool needNewOrder = false;
if ( Entity.NextGuardingOffsetIndex >= Entity.GuardingOffsets.Count )
Entity.NextGuardingOffsetIndex = 0;
ArcenPoint offset = Entity.GuardingOffsets[Entity.NextGuardingOffsetIndex];
ArcenPoint targetPoint = guarded.WorldLocation + offset;
if ( order == null )
{
if ( !Entity.GetIsWithinRangeOf_VeryBasicCheckOnly( targetPoint, ExternalConstants.Instance.EXTRA_SPACE_FOR_MOVEMENT_ORDERS_BETWEEN_SHIPS ) )
needNewOrder = true;
}
else if ( order.RelatedSquad.PrimaryKeyID != guarded.PrimaryKeyID &&
!order.RelatedPoint.GetHasAnyChanceOfBeingInRange( targetPoint, ExternalConstants.Instance.EXTRA_SPACE_FOR_MOVEMENT_ORDERS_BETWEEN_SHIPS ) )
needNewOrder = true;
if ( needNewOrder )
{
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
EntityOrder newOrder = EntityOrder.Create_Move_Normal( Entity, targetPoint, false, OrderSource.Other );
Entity.Orders.QueueOrder( Entity, newOrder );
}
else if ( Entity.GuardingOffsets.Count > 1 && order == null )
Entity.NextGuardingOffsetIndex++;
}
}
#endregion
#region Guard_Guardian_AnchoredLogic
private void Guard_Guardian_AnchoredLogic( ArcenSimContext Context, GameEntity_Squad Entity, EntityOrder order, GameEntity_Squad guarded )
{
if ( guarded == null )
{
guarded = null;
Entity.GuardingOffsets.Clear();
Entity.Guarding.PrimaryKeyID = 0;
Entity.Guarding.Ref = null;
Entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.YesClear_AndAlsoClearDecollisionMoveOrders, ClearSource.DoNotClearHumanOrders_AndDoNotClearBehaviorsIfHumanOrdersPresent );
int bestFactionIndex = Entity.PlanetFaction.GetIndexOfMostAnnoyingFaction( Context, true );
Entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, bestFactionIndex );
}
}
#endregion
#region DelegateHelper_CheckForGuardFreeingProvocation
private static ArcenPoint DelegateHelper_CheckForGuardFreeingProvocation_guardPoint;
private static GameEntity_Squad DelegateHelper_CheckForGuardFreeingProvocation_foundHit;
private static int DelegateHelper_GuardAggroDistance;
private static DelReturn DelegateHelper_CheckForGuardFreeingProvocation( GameEntity_Squad otherEntity )
{
//if ( otherEntity.PlanetFaction.Faction.Type != FactionType.Player )
// return DelReturn.Continue;
if ( !otherEntity.GetIsWithinRangeOf_VeryBasicCheckOnly( DelegateHelper_CheckForGuardFreeingProvocation_guardPoint, DelegateHelper_GuardAggroDistance ) )
return DelReturn.Continue;
if ( otherEntity.GetCurrentCloakingPoints() > 0 )
return DelReturn.Continue;
DelegateHelper_CheckForGuardFreeingProvocation_foundHit = otherEntity;
return DelReturn.Break;
}
#endregion
#region DoSystemStep
public override void DoSystemStep( FInt EffectiveDeltaTime, ArcenSimContext Context, EntitySystem System )
{
if ( System == null )
return;
if ( !System.DataForMark.IsFunctionalAtThisMarkLevel )
return;
#region tracing
bool trace = Engine_AIW2.TraceAtAll && System.ParentEntity == GameEntity_Base.CurrentlyHoveredOver && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic );
ArcenCharacterBuffer tracingBuffer = null;
if ( trace ) tracingBuffer = new ArcenCharacterBuffer();
if ( trace ) tracingBuffer.Add( "Tracing DoSystemStep for " ).Add( System.TypeData.InternalName ).Add( " from " ).Add( System.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( System.ParentEntity.PrimaryKeyID );
#endregion
if ( EffectiveDeltaTime <= FInt.Zero )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because EffectiveDeltaTime <= FInt.Zero" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
if ( System.TypeData.OnlyFiresOnDeath )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.OnlyFiresOnDeath" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
ArcenRejectionReason disabledReason = System.ForShortTermPlanning_DisabledReason;
if ( disabledReason != ArcenRejectionReason.Unknown )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because disabledReason == " ).Add( disabledReason.ToString() );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
System.TimeUntilNextShot -= EffectiveDeltaTime;
if ( System.TimeUntilNextShot > FInt.Zero )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TimeUntilNextShot > FInt.Zero" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
System.TimeUntilNextShot = FInt.Zero;
if ( System.TypeData.FiringTiming == FiringTiming.Never )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.FiringTiming == FiringTiming.Never" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
if ( System.TypeData.Category == EntitySystemCategory.Passive )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.Category == EntitySystemCategory.Passive" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
System.IsAuthorizedToFire = false;
if ( System.TypeData.Category == EntitySystemCategory.Weapon && System.TypeData.FiringTiming == FiringTiming.WhenParentEntityHit )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because this.TypeData.Category == EntitySystemCategory.Weapon and this.TypeData.FiringTiming == FiringTiming.WhenParentEntityHit" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
ArcenRejectionReason preventionReason = System.ForShortTermPlanning_CannotBeFiredReason;
if ( preventionReason != ArcenRejectionReason.Unknown )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping because preventionReason == " ).Add( preventionReason.ToString() );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
if ( System.TypeData.Category == EntitySystemCategory.Weapon )
{
bool didTrigger = ActuallyFireSalvoAtTargetPriorityList( Context, System, trace, tracingBuffer );
if ( !didTrigger )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "skipping remainder of logic because ActuallyFireSalvo returned false" );
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return;
}
}
CheckForNonSalvoActivationEffects( Context, System, trace, tracingBuffer );
#region tracing
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
}
#endregion
#region DoSystemOnDeathEffect
public override void DoSystemOnDeathEffect( ArcenSimContext Context, EntitySystem System )
{
if ( System == null )
return;
if ( System.TypeData.Category == EntitySystemCategory.Weapon )
ActuallyFireSalvoFromOnDeath( Context, System, false, null );
CheckForNonSalvoActivationEffects( Context, System, false, null );
}
#endregion
#region CheckForNonSalvoActivationEffects
public void CheckForNonSalvoActivationEffects( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer )
{
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "Tracing CheckForNonSalvoActivationEffects for " ).Add( System.TypeData.InternalName ).Add( " from " ).Add( System.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( System.ParentEntity.PrimaryKeyID );
// no current need to trace more than the call itself, add later if needed
#endregion
System.TimeUntilNextShot += (FInt)System.GetWeaponReloadTime( true );
if ( System.TypeData.SpawnsEntity != null )
System.ParentEntity.SpawnEntity( System.TypeData.SpawnsEntity, System.ParentEntity.CurrentMarkLevel,
System.ParentEntity.FleetMembership.Fleet,
0, //this is a secondary entity, so it doesn't ever get unique slots
System.ParentEntity.Orders.BehaviorRelatedFactionIndex, Context );
if ( System.TypeData.WhiteoutSeconds > 0 )
{
System.ParentEntity.Planet.WhiteoutTotalDuration = System.TypeData.WhiteoutSeconds;
System.ParentEntity.Planet.WhiteoutRemainingDuration = System.TypeData.WhiteoutSeconds;
}
}
#endregion
#region ActuallyFireSalvoAtTargetPriorityList
public bool ActuallyFireSalvoAtTargetPriorityList( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer )
{
System.LastShotFireAbortCode = 0;
//System.ParentEntity.DebugText = "TRY SALVO";
if ( System.TypeData.OnlyFiresOnDeath )
{
System.LastShotFireAbortCode = 1;
return false; //no shot was fired
}
int cloakingPointsIfMobileNPC = System.ParentEntity.TypeData.IsMobile && System.ParentEntity.PlanetFaction.Faction.Type != FactionType.Player ?
System.ParentEntity.GetCurrentCloakingPoints() : 0;
EntityOrder order = System.ParentEntity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision();
GameEntity_Squad frdAttackTarget = System.CurrentFRDTarget.GetSquad();
GameEntity_Squad mainAttackTarget = null;
if ( order != null && order.TypeData.Type == EntityOrderType.Attack )
mainAttackTarget = order.RelatedSquad.GetSquad();
else //no attack order at the moment
{
if ( cloakingPointsIfMobileNPC > 0 )
{
System.LastShotFireAbortCode = 2;
return false; //don't fire at all if we don't have a direct target and we're cloaked.
}
}
if ( mainAttackTarget != null )
{
if ( mainAttackTarget.GetHasBeenDestroyed() || mainAttackTarget.HasBeenRemovedFromSim || mainAttackTarget.SecondsSpentAsRemains > 0 || mainAttackTarget.ToBeRemovedAtEndOfThisFrame ||
mainAttackTarget.HasNotYetBeenFullyClaimed || mainAttackTarget.PlanetFaction == null || mainAttackTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject )
{
mainAttackTarget = null;
if ( System.ParentEntity.Orders.QueuedOrders.Contains( order ) )
System.ParentEntity.Orders.QueuedOrders.Remove( order );
}
}
if ( frdAttackTarget != null )
{
if ( frdAttackTarget.GetHasBeenDestroyed() || frdAttackTarget.HasBeenRemovedFromSim || frdAttackTarget.SecondsSpentAsRemains > 0 || frdAttackTarget.ToBeRemovedAtEndOfThisFrame ||
frdAttackTarget.HasNotYetBeenFullyClaimed || frdAttackTarget.PlanetFaction == null || frdAttackTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject )
{
frdAttackTarget = null;
System.CurrentFRDTarget.SetInternalRef( null );
}
}
//System.ParentEntity.DebugText += " SHOOT?";
if ( mainAttackTarget == null && frdAttackTarget == null && ( System.targetPriorityList == null || System.targetPriorityList.Count == 0 ) )
{
System.LastShotFireAbortCode = 3;
return false; //no shot was fired
}
bool chooseNewFRDIfPossible = ( mainAttackTarget == null && frdAttackTarget == null && System.ParentEntity.Orders.Behavior == EntityBehaviorType.Attacker_Full );
bool chooseNewAttackMoveIfPossible = ( mainAttackTarget == null && frdAttackTarget == null && System.ParentEntity.Orders.Behavior == EntityBehaviorType.Attacker_PursueOnlyInRange );
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "Tracing ActuallyFireSalvo for " ).Add( System.TypeData.InternalName ).Add( " from " ).Add( System.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( System.ParentEntity.PrimaryKeyID );
#endregion
int totalShotsToFire = System.TypeData.ShotsPerSalvo + (System.ParentEntity.ExtraStackedSquadsInThis / ExternalConstants.Instance.Balance_StacksPerBonusShot);
ArcenPoint originPoint = System.GetWorldLocation();
int spacingMultiplier = 40;
int withinSalvoLowEnd = 5 * spacingMultiplier;
int withinSalvoHighEnd = 10 * spacingMultiplier;
Planet myPlanet = System.ParentEntity.Planet;
//System.ParentEntity.DebugText += " SHOOT!";
bool hasPlayedSoundYet = false;
//int runningTargetIndex = 0;
int numberOfShotsFired = 0;
bool hadAnyOutOfRange = false;
#region tracing
if ( trace )
{
//tracingBuffer.Add( "\n" ).Add( "\t").Add( "damageMultiplierFromSubsquads" ).Add( ":" ).Add( damageMultiplierFromSubsquads );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "totalShotsToFire" ).Add( ":" ).Add( totalShotsToFire );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "System.targetPriorityList.Count" ).Add( ":" ).Add( System.targetPriorityList.Count );
}
#endregion
{
FInt runningDelay = FInt.Zero;
int targetIndex = -2;
int attemptCount = 100;
while ( numberOfShotsFired < totalShotsToFire && targetIndex < System.targetPriorityList.Count && attemptCount-- > 0 )
{
//System.ParentEntity.DebugText += " " + targetIndex;
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetIndex " ).Add( targetIndex );
#endregion
GameEntity_Squad potentialTarget;
{
if ( System.targetPriorityList.Count <= targetIndex )
break;
if ( targetIndex == -2 ) //try to attack the main focus of this entity if it has orders
{
if ( mainAttackTarget == null )
{
targetIndex++;
continue;
}
potentialTarget = mainAttackTarget;
}
else if ( targetIndex == -1 ) //try to attack the FRD focus of this system
{
if ( frdAttackTarget == null || frdAttackTarget == mainAttackTarget )
{
targetIndex++; //can't attack the same thing more than once
continue;
}
potentialTarget = frdAttackTarget;
}
else
{
if ( cloakingPointsIfMobileNPC > 0 && numberOfShotsFired == 0 )
return false; //don't fire at all if we don't have a direct target that we can hit and we're cloaked
potentialTarget = System.targetPriorityList[targetIndex].GetSquad();
if ( potentialTarget == null || potentialTarget == frdAttackTarget || potentialTarget == mainAttackTarget )
{
targetIndex++; //can't attack the same thing more than once
continue;
}
}
//System.ParentEntity.DebugText += " attempt";
#region tracing
if ( trace )
{
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetIndex" ).Add( ":" ).Add( targetIndex );
//tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetSquadID" ).Add( ":" ).Add( targetPriority.TargetSquadID );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "autoTarget" ).Add( ":" ).Add( potentialTarget?.TypeData.InternalName ?? "null" );
}
#endregion
if ( potentialTarget == null || potentialTarget.HasBeenRemovedFromSim || potentialTarget.ToBeRemovedAtEndOfThisFrame ||
potentialTarget.SecondsSpentAsRemains > 0 || potentialTarget.GetHasBeenDestroyed() ||
potentialTarget.HasNotYetBeenFullyClaimed || potentialTarget.PlanetFaction == null || potentialTarget.PlanetFaction.Faction.Type == FactionType.NaturalObject )
{
//if ( potentialTarget != null )//&& potentialTarget.TypeData.InternalName.Contains( "Spider" ) )
// System.ParentEntity.DebugText += potentialTarget.HasBeenRemovedFromSim + " " + potentialTarget.ToBeRemovedAtEndOfThisFrame + " " + potentialTarget.SecondsSpentAsRemains + " " + potentialTarget.TypeData.InternalName;
//else
// System.ParentEntity.DebugText += " null target!";
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "target null, done here" );
#endregion
//this thing is dead or dying, don't shoot it and instead shoot something else if we can
if ( targetIndex >= 0 )
System.targetPriorityList.RemoveAt( targetIndex ); //don't increment targetIndex if was an actual target
else
targetIndex++; //do increase targetIndex if it was an auto-target from less than zero
continue;
}
if ( potentialTarget.Planet != myPlanet )
{
//System.ParentEntity.DebugText += potentialTarget.Planet.Name + " " + myPlanet.Name + " " + potentialTarget.TypeData.InternalName;
//If you suspect foul play with a background thread putting junk in here, uncomment this. Results on 8/29/2018 were good.
//ArcenDebugging.ArcenDebugLog( "ERROR: Firing between planets! " + System.TypeData.InternalName + " tried to shoot a " + autoTarget.TypeData.InternalName +
// " from planet " + ( myPlanet == null ? "NULL" : myPlanet.Name ) + " to planet " + ( autoTarget.Planet == null ? "NULL" : autoTarget.Planet.Name ), Verbosity.ShowAsError );
//this thing left the current planet, don't shoot it and instead shoot something else if we can
if ( targetIndex >= 0 )
System.targetPriorityList.RemoveAt( targetIndex ); //don't increment targetIndex if was an actual target
else
targetIndex++; //do increase targetIndex if it was an auto-target from less than zero
continue;
}
//if it would be overkilled and it's not under a shield, ignore it for now.
if ( potentialTarget.ProtectingShields.Count == 0 && potentialTarget.GetExpectsToBeOverkilled( 0 ) )
{
targetIndex++; //we will skip it, but we will not take it out of our list just yet since the overkill may not really happen
continue;
}
targetIndex++;
}
//the first one is probably the best
if ( chooseNewFRDIfPossible )
{
System.CurrentFRDTarget.SetInternalRef( potentialTarget );
chooseNewFRDIfPossible = false;
}
if ( !System.GetIsTargetInRange( potentialTarget, RangeCheckType.ForActualFiring ) )
{
hadAnyOutOfRange = true;
//System.ParentEntity.DebugText += " not in range";
continue; //not all of them will be in range! This is ok and expected. Possibly none of them will be in range!
}
//the first one is probably the best -- difference is MUST be in range
if ( chooseNewAttackMoveIfPossible )
{
System.CurrentFRDTarget.SetInternalRef( potentialTarget );
chooseNewAttackMoveIfPossible = false;
}
numberOfShotsFired++;
//System.ParentEntity.DebugText += " FIRE!";
InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, potentialTarget, originPoint,
Context, System, trace, tracingBuffer, withinSalvoLowEnd, withinSalvoHighEnd );
}
}
if ( numberOfShotsFired > 0 )
{
System.LastGameSecondIFiredShot = World_AIW2.Instance.GameSecond;
// OLD LOGIC: if could not fire full salvo, do partial refund on reload timer
// Chris's new logic, for now at least: you just are out of luck, better luck next time. ;)
//if ( numberOfShotsFired < totalShotsToFire && totalShotsToFire > 0 )
//{
// FInt percentUnused = ( (FInt)totalShotsToFire - (FInt)numberOfShotsFired ) / (FInt)totalShotsToFire;
// System.TimeUntilNextShot -= percentUnused * (FInt)System.GetWeaponReloadTime(true);
//}
if ( System.ParentEntity.GetMaxCloakingPoints() > 0 )
{
if ( System.ParentEntity.GetCurrentCloakingPoints() > 0 )
{
int pointsLost = ( System.ParentEntity.GetMaxCloakingPoints() * ExternalConstants.Instance.Balance_CurrentCloakingPercentLossFromFiring ).GetNearestIntPreferringHigher();
if ( pointsLost < 1 )
pointsLost = 1;
System.ParentEntity.CloakingPointsLost += pointsLost;
}
System.ParentEntity.GameSecondOfLastCloakingPointLoss = World_AIW2.Instance.GameSecond;
}
return true;
}
else //no shot was fired!
{
if ( hadAnyOutOfRange )
System.LastShotFireAbortCode = 4;
else
System.LastShotFireAbortCode = 5;
//in cases where we didn't fire a shot, wait a random amount of time between 0 and 0.2 seconds before trying again
//this adds more flavor into how ships shoot, for one, but it also prevents hammering this method every frame
System.TimeUntilNextShot = FInt.FromParts( 0, Context.RandomToUse.Next( 0, 100 ) ) +
FInt.FromParts( 0, Context.RandomToUse.Next( 0, 100 ) );
return false;
}
}
#region ActuallyFireSalvoFromOnDeath
public void ActuallyFireSalvoFromOnDeath( ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer )
{
bool hasPlayedSoundYet = false;
FInt runningDelay = FInt.Zero;
ArcenPoint originPoint = System.GetWorldLocation();
InternalCreateActualShotForSalvo( ref hasPlayedSoundYet, ref runningDelay, System.ParentEntity, originPoint,
Context, System, trace, tracingBuffer, 1, 3 );
}
#endregion
#region InternalCreateActualShotForSalvo
private void InternalCreateActualShotForSalvo( ref bool hasPlayedSoundYet, ref FInt runningDelay, GameEntity_Squad target, ArcenPoint originPoint,
ArcenSimContext Context, EntitySystem System, bool trace, ArcenCharacterBuffer tracingBuffer, int withinSalvoLowEnd, int withinSalvoHighEnd )
{
#region Check To See If Relevant: On Background Planets, Shots Should Insta-Hit And Not Spawn Entities
if ( System.ParentEntity != null && System.ParentEntity.Planet != null && System.ParentEntity.Planet.BattleStatus_ShotsInstaHitUnlessPlayer )
{
if ( System.ParentEntity.PlanetFaction != null && System.ParentEntity.PlanetFaction.Faction != null &&
System.ParentEntity.PlanetFaction.Faction.Type != FactionType.Player )
{
if ( target == null || ( target.PlanetFaction != null && target.PlanetFaction.Faction != null &&
target.PlanetFaction.Faction.Type != FactionType.Player ) )
{
//we're firing at something OTHER than a player ship, and we're not a player ship, so do the thing
#region It IS Relevant, So Do The Insta-Hit
if ( !this.CheckForShotAOEDetonation( null, target, System, Context ) )
this.DoShotHitLogic( null, System, target, Context );
#endregion
return;
}
}
}
#endregion
GameEntity_Shot newShot = SpawnShot( System, originPoint, Context );
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "spawned actual shot:" ).Add( newShot.PrimaryKeyID ).Add( ":" ).Add( newShot.TypeData.InternalName );
#endregion
newShot.SetTarget( target );
if ( System.TypeData.FiresSalvoSequentially )
{
runningDelay += FInt.FromParts( 0, Context.RandomToUse.Next( withinSalvoLowEnd, withinSalvoHighEnd ) );
newShot.RemainingDelayUntilEntersSim = runningDelay;
}
else
newShot.RemainingDelayUntilEntersSim = FInt.Zero;
if ( !hasPlayedSoundYet )
{
hasPlayedSoundYet = true; //otherwise we spam the queue and I'm just going to discard them anyway!
newShot.PlayJustFiredSound();
}
}
#endregion
public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, ArcenSimContext Context )
{
FInt dummy;
return DoShotHitLogic( ShotOrNull, OriginSystemForShot, Target, false, (FInt)100, out dummy, Context );
}
public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, ArcenSimContext Context )
{
FInt dummy;
return DoShotHitLogic( ShotOrNull, OriginSystemForShot, Target, HonorFiniteHitCountAOE, (FInt)100, out dummy, Context );
}
public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, FInt PercentOfTotalAttackPowerForThisHit, out FInt PercentOfTotalAttackPowerUsedForThisHit, ArcenSimContext Context )
{
return DoShotHitLogic( ShotOrNull, OriginSystemForShot, Target, false, PercentOfTotalAttackPowerForThisHit, out PercentOfTotalAttackPowerUsedForThisHit, Context );
}
public override bool DoShotHitLogic( GameEntity_Shot ShotOrNull, EntitySystem OriginSystemForShot, GameEntity_Squad Target, bool HonorFiniteHitCountAOE, FInt PercentOfTotalAttackPowerForThisHit, out FInt PercentOfTotalAttackPowerUsedForThisHit, ArcenSimContext Context )
{
PercentOfTotalAttackPowerUsedForThisHit = FInt.Zero;
if ( !OriginSystemForShot.GetCanHitByDesireOrNot_AndIAlreadyKnowIAmAWeapon( Target ) )
return false;
bool wasAlive = !Target.GetHasBeenDestroyed();
GameEntity_Squad protectingShieldThatTookTheHit = null;
if ( OriginSystemForShot != null )
{
OriginSystemForShot.LastGameSecondMyShotHit = World_AIW2.Instance.GameSecond;
OriginSystemForShot.LastTotalDamageMyShotDidCaused = 0;
OriginSystemForShot.LastDamageAbortCode = 0;
}
{
#region tracing
bool trace = Engine_AIW2.TraceAtAll;
if ( trace )
{
if ( Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && OriginSystemForShot.ParentEntity == GameEntity_Base.CurrentlyHoveredOver )
{ } //trace this
else if ( Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.ShotHitLogic ) )
{ } //trace this
else
trace = false;
}
ArcenCharacterBuffer tracingBuffer = null;
if ( trace ) tracingBuffer = new ArcenCharacterBuffer();
if ( trace ) tracingBuffer.Add( "Tracing DoHitLogic for shot from " ).Add( OriginSystemForShot.TypeData.InternalName ).Add( " from " ).Add( OriginSystemForShot.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( OriginSystemForShot.ParentEntity.PrimaryKeyID );
#endregion
int attackPowerAgainstThisTarget = OriginSystemForShot.GetAttackPowerAgainst( Target, tracingBuffer, true );
int adjustedAttackPower = ((attackPowerAgainstThisTarget * PercentOfTotalAttackPowerForThisHit) / 100).IntValue;
#region tracing
if ( trace ) tracingBuffer.Add( "\n" ).Add( "int adjustedAttackPower = ( ( attackPowerAgainstThisTarget " + attackPowerAgainstThisTarget + " * PercentOfTotalAttackPowerForThisHit " + PercentOfTotalAttackPowerForThisHit +" ) / 100 ).IntValue = " ).Add( adjustedAttackPower );
#endregion
int actualDamageDone, damageAbortCode;
protectingShieldThatTookTheHit = Target.TakeDamage( adjustedAttackPower, OriginSystemForShot, ShotOrNull, false, false, HonorFiniteHitCountAOE, OriginSystemForShot.TypeData.MaxStacksToKill, out actualDamageDone, out damageAbortCode, Context );
if ( OriginSystemForShot != null )
{
OriginSystemForShot.LastTotalDamageMyShotDidCaused += actualDamageDone;
if ( damageAbortCode != 0 )
OriginSystemForShot.LastDamageAbortCode = damageAbortCode;
}
#region tracing
if ( trace )
{
tracingBuffer.Add( "\n" ).Add( "actualDamageDone = " ).Add( actualDamageDone );
ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
}
#endregion
if ( actualDamageDone <= 0 )
return false;
if ( attackPowerAgainstThisTarget > 0 ) // it will be, but just to be sure
{
if ( actualDamageDone == adjustedAttackPower )
PercentOfTotalAttackPowerUsedForThisHit = PercentOfTotalAttackPowerForThisHit; // otherwise sometimes precision errors can make it look like the whole shot was not used
else
PercentOfTotalAttackPowerUsedForThisHit += ((FInt)(actualDamageDone * 100) / (FInt)attackPowerAgainstThisTarget);
}
if ( OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt != FInt.Zero && actualDamageDone > 0 )
{
if ( OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt < FInt.Zero )
{
int selfDamage = (actualDamageDone * -OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt).IntValue;
OriginSystemForShot.ParentEntity.TakeDamage( selfDamage, null, null, true, Context );
}
else
{
int selfHealing = (actualDamageDone * OriginSystemForShot.ParentEntity.TypeData.HealthChangePerDamageDealt).IntValue;
selfHealing -= OriginSystemForShot.ParentEntity.TakeHullRepair( selfHealing );
if ( selfHealing > 0 )
OriginSystemForShot.ParentEntity.TakeShieldRepair( selfHealing );
}
}
}
World_AIW2.Instance.TotalShotsHit++;
bool wholeSquadKilled = wasAlive && Target.GetHasBeenDestroyed();
int shipsKilled = wholeSquadKilled ? 1 : 0;
if ( ShotOrNull != null && ShotOrNull.Planet != null && ShotOrNull.Planet.BattleStatus == PlanetBattleStatus.Tier1_PlayerLookingAtMe )
{
if ( ShotOrNull.VisualLinkObject_GenericOnly != null || ShotOrNull.InstancedRenderer != null )
{
if ( ShotOrNull.InstancedRenderer != null )
ShotOrNull.InstancedRenderer.ReactToShotHittingSquad( Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled );
else
Engine_AIW2.Instance.PresentationLayer.ReactToShotHittingSquad( ShotOrNull, Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled );
}
else
{
if ( Target.InstancedRenderer != null )
Target.InstancedRenderer.ReactToShotHittingSquad( Target, protectingShieldThatTookTheHit, shipsKilled, wholeSquadKilled );
}
}
else
{
if ( (shipsKilled > 0 || wholeSquadKilled) && Target.TypeData.Category == GameEntityCategory.Ship )
{
//ArcenDebugging.ArcenDebugLog( "Killed ship! " + Target.TypeData.InternalName + " InstancedRenderer = " + (Target.InstancedRenderer == null ? "null" : "ok"), Verbosity.DoNotShow );
Target.PlayJustDiedSoundIfNotOnCurrentLocalPlanet( Context, wholeSquadKilled );
}
}
return true;
}
public override bool CheckForShotAOEDetonation( GameEntity_Shot ShotOrNull, GameEntity_Squad TargetOrNull, EntitySystem OriginSystemForShot, ArcenSimContext Context )
{
int aoe = OriginSystemForShot.DataForMark.ShotAreaOfEffect;
if ( aoe > 0 )
{
PlanetFaction myFaction = OriginSystemForShot.ParentEntity.PlanetFaction;
if ( myFaction == null )
return true; //didn't work, but still an AOE shot so report true
PlanetFaction fac;
ArcenPoint myLoc = (ShotOrNull != null ? ShotOrNull.WorldLocation : OriginSystemForShot.ParentEntity.WorldLocation );
Planet myPlanet = (ShotOrNull != null ? ShotOrNull.Planet : OriginSystemForShot.ParentEntity.Planet);
Context.WorkingAOETargetsToHitList.Clear();
int distance;
for ( int i = 0; i < myPlanet.Factions.Count; i++ )
{
fac = myPlanet.Factions[i];
if ( fac == null )
continue;
//doing this only once per faction, and more centrally, is quite efficient when there's a lot of entities
if ( !OriginSystemForShot.TypeData.AOEHitsFriendlyTargets &&
!OriginSystemForShot.ParentEntity.PlanetFaction.GetIsHostileTowards( fac ) )
continue;
fac.Entities.DoForEntities( delegate ( GameEntity_Squad otherEntity )
{
distance = aoe + otherEntity.DataForMark.Radius;
//first very raw check, good for weeding out
if ( !otherEntity.WorldLocation.GetHasAnyChanceOfBeingInRange( myLoc, distance ) )
return DelReturn.Continue;
if ( otherEntity.GetDamageForbiddenToThisTargetByTargetingRules( myFaction, TargetOrNull != null && otherEntity.PrimaryKeyID == TargetOrNull.PrimaryKeyID ) )
return DelReturn.Continue;
//all righty, it's close so do the more expensive check (still not that expensive)
if ( otherEntity.WorldLocation.GetDistanceTo( myLoc, false ) > distance )
return DelReturn.Continue;
Context.WorkingAOETargetsToHitList.Add( otherEntity );
return DelReturn.Continue;
} );
}
int numberOfTargetsToHit = Context.WorkingAOETargetsToHitList.Count;
int maxTargets = OriginSystemForShot.TypeData.MaximumNumberOfTargetsHitPerShot;
if ( maxTargets > 0 )
numberOfTargetsToHit = maxTargets;
if ( numberOfTargetsToHit <= Context.WorkingAOETargetsToHitList.Count )
Engine_Universal.Randomize( Context.WorkingAOETargetsToHitList, Context.RandomToUse, 3 );
if ( OriginSystemForShot.TypeData.AOESpreadsDamageAmongAvailableTargets )
{
if ( Context.WorkingAOETargetsToHitList.Count > 0 )
{
int totalOutput = OriginSystemForShot.DataForMark.CalculateActualDamagePerShot( OriginSystemForShot.ParentEntity );
int remainingOutput = totalOutput;
int maxLoopCount = 100;
bool checkAgain = true;
while ( remainingOutput > 0 && checkAgain && maxLoopCount-- > 0 )
{
checkAgain = false;
FInt portionPerEachOutOf100 = Mat.Max( (FInt)10, (FInt)100 / Context.WorkingAOETargetsToHitList.Count ); // always tries to apply at least 10%, to avoid this getting into fiddling small change and burning inordinate cpu
// Badger doesn't really like this logic for AOE shots that do a huge amount of damage, but he concedes it is more efficient
// and if there are a ton of targets then they are probably stacked, so heavy damage to a single target is probably fine
for ( int i = 0; i < Context.WorkingAOETargetsToHitList.Count; i++ )
{
FInt portionUsedOutOf100;
if ( remainingOutput <= 0 ) //if we've already done the total damage we were allowed to do, we're done
{
checkAgain = false;
continue;
}
// ArcenDebugging.ArcenDebugLogSingleLine(OriginSystemForShot.TypeData.GetDisplayName() + " is trying to do " + portionPerEachOutOf100 + "% damage to " + Context.WorkingAOETargetsToHitList[i].ToString() +". remaining damage before the hit: " + remainingOutput , Verbosity.DoNotShow );
EntitySimLogic.Instance.DoShotHitLogic( ShotOrNull, OriginSystemForShot, Context.WorkingAOETargetsToHitList[i], portionPerEachOutOf100, out portionUsedOutOf100, Context );
// ArcenDebugging.ArcenDebugLogSingleLine("\tPortion used: " + portionUsedOutOf100, Verbosity.DoNotShow );
if ( portionUsedOutOf100 <= 0 )
{
Context.WorkingAOETargetsToHitList.RemoveAt( i-- );
continue;
}
int damageDone = Math.Max( 1, ((portionUsedOutOf100 * totalOutput) / 100).GetNearestIntPreferringHigher() );
if ( damageDone <= 0 )
{
Context.WorkingAOETargetsToHitList.RemoveAt( i-- );
continue;
}
remainingOutput -= damageDone;
checkAgain = true;
}
}
}
}
else
{
Context.WorkingAOETargetsThatHaveBeenHitList.Clear();
for ( int i = 0; i < Context.WorkingAOETargetsToHitList.Count; i++ )
{
if ( numberOfTargetsToHit <= 0 )
break;
if ( EntitySimLogic.Instance.DoShotHitLogic( ShotOrNull, OriginSystemForShot, Context.WorkingAOETargetsToHitList[i], true, Context ) )
numberOfTargetsToHit--;
}
Context.WorkingAOETargetsThatHaveBeenHitList.Clear();
}
if ( ShotOrNull != null )
{
ShotOrNull.PostExecution_EmitOnAOECount++;
ShotOrNull.PostExecution_AOESimLocation = ShotOrNull.WorldLocation;
}
return true;
}
else if ( OriginSystemForShot != null && OriginSystemForShot.TypeData.BeamLengthMultiplier > FInt.Zero )
{
if ( TargetOrNull == null )
return true; //didn't work, but still an AOE shot so report true
GameEntity_Squad targetEntity = TargetOrNull;
#region tracing
bool trace = Engine_AIW2.TraceAtAll && Engine_AIW2.TracingFlags.Has( ArcenTracingFlags.IndividualLogic ) && OriginSystemForShot.ParentEntity == GameEntity_Base.CurrentlyHoveredOver;
ArcenCharacterBuffer tracingBuffer = null;
if ( trace ) tracingBuffer = new ArcenCharacterBuffer();
if ( trace ) tracingBuffer.Add( "Tracing CheckForAOEDetonation for shot from " ).Add( OriginSystemForShot.TypeData.InternalName ).Add( " from " ).Add( OriginSystemForShot.ParentEntity.TypeData.InternalName ).Add( " #" ).Add( OriginSystemForShot.ParentEntity.PrimaryKeyID );
#endregion
if ( OriginSystemForShot.TypeData.HitsAllIntersectingTargets )
{
ArcenPoint originPoint = OriginSystemForShot.ParentEntity.WorldLocation;
ArcenPoint targetPoint = targetEntity.WorldLocation;
AngleDegrees angle = originPoint.GetAngleToDegrees( targetPoint );
int beamLength = (OriginSystemForShot.DataForMark.CalculateActualRange( OriginSystemForShot.ParentEntity ) * OriginSystemForShot.TypeData.BeamLengthMultiplier).IntValue;
int beamCount = OriginSystemForShot.TypeData.NumberBeamsToFire;
AngleDegrees beamBeginAngle = beamCount == 1 ? angle : angle.Add( -((OriginSystemForShot.TypeData.DegreesOffsetPerBeam * beamCount) / 2) );
#region tracing
if ( trace )
{
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "originPoint" ).Add( ":" ).Add( originPoint.ToString() );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "targetPoint" ).Add( ":" ).Add( targetPoint.ToString() );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "angle" ).Add( ":" ).Add( angle.ToString() );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "beamLength" ).Add( ":" ).Add( beamLength );
tracingBuffer.Add( "\n" ).Add( "\t" ).Add( "beamBeginAngle" ).Add( ":" ).Add( beamBeginAngle.ToString() );
}
#endregion
EntityBeamWeaponLineSet resultingBeamWeaponSet = EntityBeamWeaponLineSet.Create();
resultingBeamWeaponSet.TypeData = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.BeamCannon];
resultingBeamWeaponSet.OriginPoint = originPoint;
AngleDegrees workingAngle = beamBeginAngle;
int maxTargets = OriginSystemForShot.TypeData.MaximumNumberOfTargetsHitPerShot != 0 ? OriginSystemForShot.TypeData.MaximumNumberOfTargetsHitPerShot : -1;
for ( int i = 0; i < beamCount; i++, workingAngle += OriginSystemForShot.TypeData.DegreesOffsetPerBeam )
{
//GetPointAtAngleAndDistance is not remotely precise enough for our purposes here!
ArcenPoint endPoint = originPoint.GetPointAtAngleAndDistance( workingAngle, beamLength );
List intersectingTargets = OriginSystemForShot.GetEnemyEntitiesIntersectingInstaFireConicalShot( originPoint, endPoint, beamLength, targetEntity, Context, tracingBuffer );
for ( int k = 0; k < intersectingTargets.Count; k++ )
{
// If we have a max amount of targets, stop upon reaching that amount.
if ( maxTargets > 1 && k >= maxTargets )
{
endPoint = intersectingTargets[k - 1].WorldLocation;
break;
}
GameEntity_Squad intersectedTarget = intersectingTargets[k];
EntitySimLogic.Instance.DoShotHitLogic( ShotOrNull, OriginSystemForShot, intersectedTarget, Context );
}
intersectingTargets.Clear();
//UnityEngine.Debug.Log( i + " " + beamCount + " " + workingAngle + " " + endPoint + " " + originPoint.GetPointAtAngleAndDistance( workingAngle, beamLength ) );
resultingBeamWeaponSet.TargetPoints.Add( endPoint );
}
OriginSystemForShot.ParentEntity.Visual_NextOutgoingBeamWeaponLineSets.Add( resultingBeamWeaponSet );
#region tracing
if ( trace ) ArcenDebugging.ArcenDebugLogSingleLine( tracingBuffer.ToString(), Verbosity.DoNotShow );
#endregion
return true;
}
else //only hits the target
{
EntityBeamWeaponLineSet resultingBeamWeaponSet = EntityBeamWeaponLineSet.Create();
resultingBeamWeaponSet.TypeData = EntityLineTypeTable.Instance.RowsByHardcodedType[EntityLineHardcodedType.Laser];
resultingBeamWeaponSet.OriginPoint = OriginSystemForShot.ParentEntity.WorldLocation;
resultingBeamWeaponSet.TargetPoints.Add( targetEntity.WorldLocation );
OriginSystemForShot.ParentEntity.Visual_NextOutgoingBeamWeaponLineSets.Add( resultingBeamWeaponSet );
return true;
}
}
//not an AOE shot
return false;
}
public GameEntity_Shot SpawnShot( EntitySystem System, ArcenPoint StartingLocation, ArcenSimContext Context )
{
return SpawnShot( System, System.TypeData.ShotTypeData, StartingLocation, Context );
}
public GameEntity_Shot SpawnShot( EntitySystem System, GameEntityTypeData effectiveType, ArcenPoint StartingLocation, ArcenSimContext Context )
{
GameEntity_Shot newShot = GameEntity_Shot.CreateNew( System.ParentEntity.PlanetFaction, effectiveType, StartingLocation, Context );
newShot.Origin = System;
return newShot;
}
#endregion
#region DoWormholeTraversalLogic
public override void DoWormholeTraversalLogic( ArcenSimContext Context )
{
bool didShipsGoThroughWormhole = false;
int currentPlanetIndex = -1;
ArcenPoint shipWormholePoint = ArcenPoint.ZeroZeroPoint;
if ( PlayerAccount.Local != null )
currentPlanetIndex = PlayerAccount_AIW2.Local.ViewingPlanetIndex;
//bool onGalaxyMap = Engine_AIW2.Instance.CurrentGameViewMode != GameViewMode.MainGameView;
for ( int i = 0; i < World_AIW2.Instance.Galaxies[0].Planets.Count; i++ )
{
Planet planet = World_AIW2.Instance.Galaxies[0].Planets[i];
if ( !planet.BattleStatus_ProcessThisSimStep )
continue;
for ( int j = 0; j < planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame.Count; j++ )
{
GameEntity_Squad entity = planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame[j];
EntityOrder order = entity.RemoveInvalidatedOrdersAndReturnFirstValid_ThatIsNotDecollision();
if ( order == null || order.TypeData.Type != EntityOrderType.Wormhole )
continue;
if (entity.ActiveHack_Target != 0)
continue;
if ( entity.GetHasBeenDestroyed() || entity.ToBeRemovedAtEndOfThisFrame )
continue;
Planet myPlanet = entity.Planet;
if ( myPlanet != planet )
continue;
GameEntity_Other thisSideWormhole = order.GetWormholeToOtherPlanet( entity );
if ( thisSideWormhole == null )
continue;
Planet targetPlanet = thisSideWormhole.GetLinkedPlanet();
if ( targetPlanet == null || targetPlanet == myPlanet )
continue;
GameEntity_Other otherSideWormhole = targetPlanet.GetWormholeTo( myPlanet );
if ( otherSideWormhole == null )
continue;
PlanetFaction targetPlanetDestinationFaction = targetPlanet.GetPlanetFactionForFaction( entity.PlanetFaction.Faction );
if ( !didShipsGoThroughWormhole )
{
if ( currentPlanetIndex == targetPlanet.PlanetIndex )
{
shipWormholePoint = otherSideWormhole.WorldLocation;
didShipsGoThroughWormhole = true;
}
else if ( currentPlanetIndex == myPlanet.PlanetIndex )
{
shipWormholePoint = thisSideWormhole.WorldLocation;
didShipsGoThroughWormhole = true;
}
}
//ArcenPoint exitAnimationStartingPoint = entity.WorldLocation;
//AngleDegrees exitAnimationAngle = Engine_AIW2.Instance.CombatCenter.GetAngleToDegrees( thisSideWormhole.WorldLocation );
//int exitAnimationDistance = Engine_AIW2.Instance.CombatCenter.GetDistanceTo( thisSideWormhole.WorldLocation, false ) * 1000;
//ArcenPoint exitAnimationTargetPoint = Engine_AIW2.Instance.CombatCenter.GetPointAtAngleAndDistance( exitAnimationAngle, exitAnimationDistance );
//CHRIS_TODO: animation of ship zipping off into the distance from exitAnimationStartingPoint to exitAnimationTargetPoint (to get those, uncomment the four lines above)
//bool isPlayerFactionEntity = entity.PlanetFaction.Faction.Type == FactionType.Player;
//bool shouldSelectOnOtherSide = false;
//if ( onGalaxyMap && isPlayerFactionEntity &&
// entity.GetIsSelected() )
// shouldSelectOnOtherSide = true;
entity.ForceUnselect( true );
entity.RemoveFromSpatialPartitioning( false, InstancedRendererDeactivationReason.WentThroughWormhole );
//AngleDegrees angleToWormhole = Engine_AIW2.Instance.CombatCenter.GetAngleToDegrees( otherSideWormhole.WorldLocation );
//int distanceToWormhole = Engine_AIW2.Instance.CombatCenter.GetDistanceTo( otherSideWormhole.WorldLocation, false );
ArcenPoint exitPoint = otherSideWormhole.WorldLocation;
entity.GameSecondEnteredThisPlanet = World_AIW2.Instance.GameSecond;
entity.SetWorldLocation( exitPoint );
entity.PlanetFaction.SwitchToFaction( entity, targetPlanetDestinationFaction );
entity.AddToNewPlanet( targetPlanet );
//clear any decollision orders that were previously in place before going through the wormhole.
entity.Orders.ClearOrders( ClearBehavior.DoNotClearBehaviors, ClearDecollisionOnParent.OnlyClearDecollisionOrdersAndNothingElse, ClearSource.DoNotClearAnythingExceptDecollision );
//if ( shouldSelectOnOtherSide )
// World_AIW2.Instance.EntitiesToSelectNextFrame.Add( entity );
if ( entity.CurrentlyStrongestTractorSourceHittingThese.Count > 0 )
{
for ( int k = 0; k < entity.CurrentlyStrongestTractorSourceHittingThese.Count; k++ )
{
int id = entity.CurrentlyStrongestTractorSourceHittingThese[k];
GameEntity_Squad tractoredEntity = World_AIW2.Instance.GetEntityByID_Squad( id );
if ( tractoredEntity == null )
continue;
if ( tractoredEntity.GetMatches( EntityRollupType.ProjectsForcefield ) && tractoredEntity.ShieldPointsLost < tractoredEntity.DataForMark.ShieldPoints )
continue; // can tractor something with a shield, but you can't actually tow it
if ( tractoredEntity.Planet == targetPlanet ) // might already be there if both are still mobile and both are trying to transit the same wormhole
continue;
tractoredEntity.RemoveFromSpatialPartitioning( false, InstancedRendererDeactivationReason.DraggedThroughWormholeByTractorBeam );
tractoredEntity.PlanetFaction.SwitchToFaction( tractoredEntity, targetPlanet.GetPlanetFactionForFaction( tractoredEntity.PlanetFaction.Faction ) );
tractoredEntity.GameSecondEnteredThisPlanet = World_AIW2.Instance.GameSecond;
//for tractored entities, pop them out of the wormhole at a random place near the exitPoint.
//It might be more efficient to calculate this range elsewhere and cache it?
int tractorRange = 0;
for ( int index = 0; index < entity.Systems.Count; index++)
{
EntitySystem system = entity.Systems[index];
if ( system.TypeData.ForMark[entity.DataForMark.MarkLevel.Ordinal].TractorCount <= 0 )
continue;
if ( system.ForShortTermPlanning_DisabledReason != ArcenRejectionReason.Unknown )
continue;
tractorRange = Math.Max( tractorRange, system.DataForMark.TractorRange );
}
AngleDegrees angle = AngleDegrees.Create( (float)Context.RandomToUse.Next( 1, 360 ) );
ArcenPoint point = exitPoint.GetPointAtAngleAndDistance(angle, tractorRange);
tractoredEntity.SetWorldLocation( point );
tractoredEntity.AddToNewPlanet( targetPlanet );
}
}
//ArcenPoint entranceAnimationTargetPoint = entity.WorldLocation;
//AngleDegrees entranceAnimationAngle = angleToWormhole;
//int entranceAnimationDistance = distanceToWormhole * 1000;
//ArcenPoint entranceAnimationStartingPoint = Engine_AIW2.Instance.CombatCenter.GetPointAtAngleAndDistance( entranceAnimationAngle, entranceAnimationDistance );
//CHRIS_TODO: animation of ship zipping in from the distance from entranceAnimationStartingPoint to entranceAnimationTargetPoint (to get those, uncomment the four lines above)
}
planet.ShipsReadyForWormholeTraversalAtEndOfThisFrame.Clear();
}
if ( didShipsGoThroughWormhole )
Engine_AIW2.Instance.PresentationLayer.PlaySoundByType( SFXItemType_Positional.WormholeTransit, shipWormholePoint );
}
#endregion
}
}