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