# **************************************************************************** # CUI # # The Advanced Framework for Simulation, Integration, and Modeling (AFSIM) # # The use, dissemination or disclosure of data in this file is subject to # limitation or restriction. See accompanying README and LICENSE for details. # **************************************************************************** behavior pincer_fsm script_debug_writes off script_variables //debug parameters bool mDrawSteering = false; //parameters for how to initially fly pincer (1st phase, separation) string mDirection = "none"; double mPincerSpeed = 500 * MATH.MPS_PER_NMPH(); //400 kts double mPincerOffsetAngle = 70.0; //degrees //parameters for determining if to chase and how to do so (2nd phase, option A) double mEnemyFOVHalfAngle = 50.0; //degrees double mChaseAngle = 75.0; //degrees double mChaseSpeedlnsideFOV = 600 * MATH.MPS_PER_NMPH(); //600 kts double mChaseSpeedOutsideFOV = 1200 * MATH.MPS_PER_NMPH(); //1200 kts //parameters for determining if to drag (run) and how to do so (2nd phase, option B) double mRunDistance = 130 * 1852; //130nm (meters) double mRunAngleLimit = 15.0; //degrees double mDragSpeed = 800 * MATH.MPS_PER_NMPH(); //800 kts double mDragAddedAngle = -1.0; //degrees // bidding parameters (similar to pursue-target parameters) double cMIN_JOB_BID = -MATH.DOUBLE_MAX(); # double cWEIGHT_CURRENT_TARGET = 10.0; # double cWEIGHT_CLOSING_SPEED_OF = 0.5; #1.0; # double cWEIGHT_SLANT_RANGE_TO = -4.0; #-3.0; # double cWEIGHT_FUEL = 2.0; # double cWEIGHT_WEAPONS_IN_FLIGHT = 0.0; # double cWEIGHT_ANY_WEAPONS_ACTIVE = 20.0; # double cWEIGHT_THREAT_WEAPONS_ENVELOPE = 10000.0; # double cBASE_SLANT_RANGE_CONSTANT = 600000.0; //over 300 nm away [How is this used???] # double cBASE_CLOSING_SPEED_CONSTANT = 1050.0; //over 2000 kts closing speed double cMAX_SLANT_RANGE = 1000000.0; #over 539 nm away, target ranges beyond this are considered unfavorable double cWEIGHT_SLANT_RANGE_TO = 1.0; double cMIN_CLOSING_SPEED = -1050.0; #threats running away (negative closing) faster are considered unfavorable double cWEIGHT_CLOSING_SPEED = 1.0; #scale for how closing speed translates to distance double cWEIGHT_FUEL = 0.0; #try a value of 2.0 if you care about fuel double cWEIGHT_MY_WEAPONS_ACTIVE = 0.0; #changes bid if your own weapons are active on target double cWEIGHT_PEERS_WEAPONS_ACTIVE = 0.0; #changes bid if your peers weapons are active on target double cWEIGHT_THREAT_WEAPONS_ENVELOPE = 10000.0; #uses the global "mWeaponsEnvelope" array, try value of 10000 if you care about that //careful with these two parameters, they are stateful double cWEIGHT_CURRENT_TARGET = 0.0; #changes bid if you are currently targeting the threat //util var, not for user edit WsfGeoPoint mPincerPoint; WsfDraw mDraw = WsfDraw(); WsfRIPRJob mCurrentJob; WsfTrack mWorstTrack; double mWorstDist = MATH.DOUBLE_MAX(); double mWorstFOV = MATH.DOUBLE_MAX(); end_script_variables script double GetPincerDragOffset(WsfTrack aTgt) if (aTgt.Speed() >= PLATFORM.Speed()) { return 180.0; } else { return (180 - MATH.ACos(aTgt.Speed()/PLATFORM.Speed())); } end_script precondition if (!PROCESSOR.IsA_TypeOf("WSF_RIPR_PROCESSOR")) { return Failure("behavior not attached to a RIPR processor!"); } // ((WsfRIPRProcessor)PROCESSOR) #writeln_d("precondition pincer"); if (((WsfRIPRProcessor)PROCESSOR).GetRIPRCommanderProcessor().IsValid()) { mCurrentJob = ((WsfRIPRProcessor)PROCESSOR).GetRIPRCommanderProcessor().GetJobFor(TIME_NOW, ((WsfRIPRProcessor)PROCESSOR)); if (mCurrentJob.IsValid()) { // If we're supposed to fly towards a point, go for it! if (mCurrentJob.Name() == "pincer") { mPincerPoint = (WsfGeoPoint)mCurrentJob.GetData("ZonePoint"); mDirection = (string)mCurrentJob.GetData(PLATFORM.Name()); if (mPincerPoint.IsValid()) { return true; } else { writeln_d(PLATFORM.Name(), " pincer point not valid!"); return Failure("pincer job did not have valid point"); } } else { writeln_d(PLATFORM.Name(), " job not a pincer job: ", mCurrentJob.Name()); } } else { writeln_d(PLATFORM.Name(), " job not valid!"); } } else { writeln_d(PLATFORM.Name(), " commander not found!"); return Failure("no commander found"); } return Failure("current job not a valid pincer job"); end_precondition ### FSM states are evaluated after "execute" execute writeln_d(PLATFORM.Name(), " executing pincer!, T=", TIME_NOW); //provide for another channel for pursue-target jobs if (((WsfRIPRProcessor)PROCESSOR).NumJobChannels() <= 1) { ((WsfRIPRProcessor)PROCESSOR).SetNumJobChannels(2); } ((WsfRIPRProcessor)PROCESSOR).ClearTarget(); //save off the closest opponent track mWorstDist = MATH.DOUBLE_MAX(); mWorstFOV = MATH.DOUBLE_MAX(); Array targetNames = (Array)mCurrentJob.GetData("ZoneThreatNameArray"); foreach(string tgtName in targetNames) { WsfTrack tgt = GetTrackByName(PLATFORM, tgtName); if (tgt.IsValid() && tgt.LocationValid()) { double attackRange = tgt.GroundRangeTo(PLATFORM); if (attackRange < mWorstDist) { //run away, drag threats out mWorstDist = attackRange; mWorstTrack = tgt; } double fov = MATH.Fabs(tgt.RelativeBearingTo(PLATFORM)); if (fov < mWorstFOV) { mWorstFOV = fov; } } } # if (mDrawSteering == true) # { # WsfGeoPoint pt = PLATFORM.Location(); # pt.Extrapolate(heading, 185200); # mDraw.SetLayer("behavior_pincer"); # mDraw.SetDuration(((WsfRIPRProcessor)PROCESSOR).UpdateInterval()); # mDraw.SetColor(0.0, 1.0, 0.75); //green-blue # mDraw.SetLineSize(1); # mDraw.BeginLines(); # mDraw.Vertex(PLATFORM.Location()); # mDraw.Vertex(pt); # mDraw.Vertex(PLATFORM.Location()); # mDraw.Vertex(mPincerPoint); # mDraw.End(); # } end_execute script bool ShouldChase() int allOnCap = (int)mCurrentJob.GetData("all_on_cap"); if (allOnCap > 0) { return false; } int other_dragging_count = 0; if (mDirection == "right") { other_dragging_count = (int)mCurrentJob.GetData("left_drag"); } else if (mDirection == "left") { other_dragging_count = (int)mCurrentJob.GetData("right_drag"); } double diff = 0; if (mWorstTrack.IsValid() && mWorstTrack.HeadingValid()) { double b1 = mWorstTrack.Heading(); double b2 = mWorstTrack.TrueBearingTo(PLATFORM.Location()); diff = MATH.NormalizeAngleMinus180_180(b2 - b1); } if (other_dragging_count > 0 && MATH.Fabs(diff) > mEnemyFOVHalfAngle) { //the other pincer group is being engaged, turn in now writeln_d(PLATFORM.Name(), " chasing because other pair is dragging"); return true; } else if ((mDirection == "right" && diff < -mChaseAngle) || (mDirection == "left" && diff > mChaseAngle) ) { //we have already flanked, so now we can chase (regardless of other side dragging or not) writeln_d(PLATFORM.Name(), " chasing because past chase angle"); return true; } return false; end_script #show_state_transitions #first default state, start here and go back to here if confused state SEPARATE next_state DRAG int allOnCap = (int)mCurrentJob.GetData("all_on_cap"); if ( (allOnCap <= 0) && (mWorstDist < mRunDistance) && (mWorstTrack.IsValid()) && (mWorstTrack.HeadingValid()) && (MATH.Fabs(mWorstTrack.TrueBearingTo(PLATFORM)-mWorstTrack.Heading()) < mRunAngleLimit)) { return true; } return false; end_next_state next_state CHASE return ShouldChase(); end_next_state next_state SEPARATE double separate_angle = 0; if (mDirection == "left") { separate_angle = -mPincerOffsetAngle; } else if (mDirection == "right") { separate_angle = mPincerOffsetAngle; } double heading = MATH.NormalizeAngle0_360(PLATFORM.TrueBearingTo(mPincerPoint) + separate_angle); PLATFORM.GoToSpeed(mPincerSpeed); PLATFORM.TurnToHeading(heading); return true; end_next_state end_state state DRAG next_state SEPARATE if ( (!mWorstTrack.IsValid()) || (mWorstDist > (mRunDistance*1.1)) || (!mWorstTrack.HeadingValid()) || (MATH.Fabs(mWorstTrack.TrueBearingTo(PLATFORM)-mWorstTrack.Heading()) > (mRunAngleLimit*1.1))) { return true; } return false; end_next_state next_state DRAG writeln_d("dragging!"); //try to maintain distance PLATFORM.GoToSpeed(mDragSpeed); double dragOffset = GetPincerDragOffset(mWorstTrack); double heading; if (mDirection == "right") { if (mDragAddedAngle > 0) { heading = MATH.NormalizeAngle0_360(heading + mDragAddedAngle); } else { heading = MATH.NormalizeAngle0_360(PLATFORM.TrueBearingTo(mPincerPoint) + dragOffset); } } else if (mDirection == "left") { if (mDragAddedAngle > 0) { heading = MATH.NormalizeAngle0_360(heading - mDragAddedAngle); } else { heading = MATH.NormalizeAngle0_360(PLATFORM.TrueBearingTo(mPincerPoint) - dragOffset); } } PLATFORM.TurnToHeading(heading); return true; end_next_state on_entry if (mDirection == "right") { int count = (int)mCurrentJob.GetData("right_drag"); mCurrentJob.SetData("right_drag",count+1); writeln_d(PLATFORM.Name(), " increased right_drag to: ", count+1); } else if (mDirection == "left") { int count = (int)mCurrentJob.GetData("left_drag"); mCurrentJob.SetData("left_drag",count+1); writeln_d(PLATFORM.Name(), " increased left_drag to: ", count+1); } end_on_entry on_exit if (mDirection == "right") { int count = (int)mCurrentJob.GetData("right_drag"); mCurrentJob.SetData("right_drag",count-1); writeln_d(PLATFORM.Name(), " decreased right_drag to: ", count-1); } else if (mDirection == "left") { int count = (int)mCurrentJob.GetData("left_drag"); mCurrentJob.SetData("left_drag",count -1); writeln_d(PLATFORM.Name(), " decreased left_drag to: ", count-1); } end_on_exit end_state state CHASE next_state SEPARATE return ! ShouldChase(); end_next_state next_state CHASE writeln_d("chasing!"); if (mWorstFOV <= mEnemyFOVHalfAngle) { PLATFORM.GoToSpeed(mChaseSpeedlnsideFOV); } else { PLATFORM.GoToSpeed(mChaseSpeedOutsideFOV); } double heading = 0; if (mWorstTrack.IsValid()) { heading = PLATFORM.TrueBearingTo(mWorstTrack); ((WsfRIPRProcessor)PROCESSOR).SetTarget(mWorstTrack); } else { heading = PLATFORM.TrueBearingTo((WsfGeoPoint)mCurrentJob.GetData("ZonePoint")); } PLATFORM.TurnToHeading(heading); return true; end_next_state end_state end_behavior