444 lines
17 KiB
Plaintext
444 lines
17 KiB
Plaintext
# ****************************************************************************
|
|
# 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.
|
|
# ****************************************************************************
|
|
|
|
|
|
include_once ../common/weapon_defs.txt
|
|
|
|
|
|
processor FL_QUANTUM_TASKER WSF_QUANTUM_TASKER_PROCESSOR
|
|
|
|
script_variables
|
|
|
|
bool mSelfCreateTasks = false; //if false, simply interpret received tasks
|
|
|
|
#########################################################################
|
|
## for evaluating weapon tasks
|
|
#########################################################################
|
|
//bool mFilterOnWeapons = true;
|
|
//double mPercentRangeMaxFire = 0.70;
|
|
double cWEIGHT_SLANT_RANGE_TO = 1.0;
|
|
double cWEIGHT_CLOSING_SPEED = 1.0;
|
|
#double cMAX_SLANT_RANGE = 20037500.0; #halfway around the earth (half circumference)
|
|
double cMAX_SLANT_RANGE = 10018750.0; #quarter way around the earth (quarter circumference)
|
|
double cMIN_CLOSING_SPEED = -1050.0;
|
|
|
|
#########################################################################
|
|
## for creating pincer tasks
|
|
#########################################################################
|
|
bool mCreatePincerTasks = false;
|
|
//for debugging
|
|
bool mDrawClusters = false;
|
|
//parameters used to determine if a pincer is appropriate
|
|
bool mRequireActiveWeapons = false; //if true, weapons must be in flight before pincer performed
|
|
double mMaxSeparationAngle = 120.0; //degrees (pincer done if we've flanked by this much)
|
|
int mNumSubsInvolved = 2; //not necessary to be an even number
|
|
double mThresholdDistance = 160 * 1852; //160nm
|
|
//clustering is performed to make sure the group of threats we are pincering against are grouped tightly together
|
|
//if more than one group exist, the 2nd, 3rd, etc... groups have to be far enough away so that they aren't a
|
|
//concern for the pincer maneuver (i.e. we wont head into them by separating around the target group)
|
|
string mClusterMethod = "HTREEMAX"; //also valid: K_MEANS, H_TREE_MIN
|
|
double mClusterDistanceLimit = 20*1852; //20nm - how close the group members have to be together
|
|
double mMinDistanceRatio = 1.15; //other groups have to be 15% farther away than target group
|
|
// parameters useful for those performing the pincer
|
|
bool mCross = false;
|
|
string mCapZoneName = ""; //specify the name of a zone in which we ignore the threat's orientation, (when they are capping in the zone)
|
|
// script variables, used by methods below, do not change, not for user edit
|
|
Array<WsfTrack> mTrackArray = Array<WsfTrack>();
|
|
Array<string> mTargetNames = Array<string>();
|
|
WsfDraw mDraw = WsfDraw();
|
|
double mMeanBearing = 0;
|
|
bool mMeanBearingValid = false;
|
|
WsfClusterManager mClusterManager;
|
|
|
|
//WsfQuantumTask mPincerTask;
|
|
|
|
WsfGeoPoint mMeanPoint;
|
|
WsfGeoPoint mSubPoint;
|
|
end_script_variables
|
|
|
|
|
|
on_initialize
|
|
mClusterManager = WsfClusterManager.Create(); // creates a cluster manager owned by this script
|
|
mClusterManager.SetClusterMethod(mClusterMethod); // default is: "K_MEANS"
|
|
mClusterManager.SetDistanceFunction("POSITION_VELOCITY"); // default is: "POSITION_ONLY"
|
|
mClusterManager.SetDistanceLimit(mClusterDistanceLimit) ;
|
|
mDraw.SetDuration(PROCESSOR.UpdateInterval());
|
|
end_on_initialize
|
|
|
|
|
|
## utility method used to draw lines around tracks in a cluster ## lines are drawn according to the convex hull of the cluster members
|
|
script void draw_cluster_hull(WsfCluster cluster)
|
|
Array<WsfGeoPoint> pts = cluster.ConvexHull();
|
|
if (pts.Size() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
WsfGeoPoint first = pts.Get(0);
|
|
mDraw.SetColor(1.0,0.5,0.0); //orange?
|
|
mDraw.SetLineStyle("solid");
|
|
mDraw.SetLineSize(2);
|
|
mDraw.BeginPolyline();
|
|
for (int j = 0; j < pts.Size(); j = j + 1 )
|
|
{
|
|
WsfGeoPoint pt = pts.Get(j);
|
|
mDraw.Vertex(pt);
|
|
}
|
|
mDraw.Vertex(first);
|
|
mDraw.End();
|
|
if (cluster.BearingValid())
|
|
{
|
|
double bearing = cluster.Bearing();
|
|
WsfGeoPoint pt = cluster.MeanLocation();
|
|
mDraw.SetColor(1.0,1.0,1.0); //white?
|
|
mDraw.BeginLines();
|
|
mDraw.Vertex(pt); pt.Extrapolate(cluster.Bearing(), 92600); //50 nautical miles
|
|
mDraw.Vertex(pt);
|
|
mDraw.End();
|
|
}
|
|
end_script
|
|
|
|
|
|
script WsfGeoPoint MeanLocation(Array<WsfAssetPerception> assets)
|
|
Vec3 mean = Vec3.Construct(0, 0, 0);
|
|
if (assets.Size() > 0)
|
|
{
|
|
double scale = 1.0/((double)assets.Size());
|
|
foreach(WsfAssetPerception asset in assets)
|
|
{
|
|
Vec3 temp = asset.Location().LocationWCS();
|
|
temp.Scale(scale);
|
|
mean = Vec3.Add(mean, temp);
|
|
}
|
|
}
|
|
return WsfGeoPoint.ConstructWCS(mean);
|
|
end_script
|
|
|
|
|
|
script Array<WsfQuantumTask> FlightLeadTaskGeneration (Array<WsfLocalTrack> TRACKS, Array<WsfAssetPerception> ASSETS )
|
|
Array<WsfQuantumTask> tasks = Array<WsfQuantumTask>();
|
|
//check if we are creating tasks or if we have a commander for that
|
|
if (mSelfCreateTasks == true)
|
|
{
|
|
writeln_d(PLATFORM.Name(), ", T=", TIME_NOW, ", creating tasks for:");
|
|
//if its us, then create weapon tasks for enemy tracks
|
|
for (int i=0; i<TRACKS.Size(); i=i+1)
|
|
{
|
|
WsfLocalTrack lt = TRACKS.Get(i);
|
|
if (lt.IsValid() && (!lt.SideValid() || lt.Side() != PLATFORM.Side()))
|
|
{
|
|
WsfQuantumTask task = WsfQuantumTask.Construct(1.0, "weapon", lt);
|
|
task.SetTaskType("WEAPON");
|
|
tasks.PushBack(task);
|
|
writeln_d("weapon task generated for: ", lt.TargetName(), ", updated time: ", lt.UpdateTime());
|
|
}
|
|
else
|
|
{
|
|
writeln_d("ignoring track for: ", lt.TargetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WsfTaskList rcvdTasks = PROCESSOR.TasksReceivedOfType("CLUSTER");
|
|
//do something with them, interpret them
|
|
foreach (WsfTask t in rcvdTasks)
|
|
{
|
|
//writeln(PLATFORM.Name(), " received CLUSTER task: ", t.TaskType(), ", resource: ", t.ResourceName());
|
|
Array<WsfTrack> perceivedThreats = PLATFORM.PerceivedThreats();
|
|
Array<string> clusterMembers = (Array<string>)t.AuxDataObject("ClusterMemberNames");
|
|
bool foundOne = false;
|
|
foreach(string member in clusterMembers)
|
|
{
|
|
foreach(WsfTrack threat in perceivedThreats)
|
|
{
|
|
if (member == threat.TargetName())
|
|
{
|
|
if (threat.Target().IsValid())
|
|
{
|
|
//create a task for this target, we have perception of it
|
|
WsfQuantumTask task = WsfQuantumTask.Construct(1.0, "weapon", threat);
|
|
task.SetTaskType("WEAPON");
|
|
tasks.PushBack(task);
|
|
foundOne = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (foundOne == false)
|
|
{
|
|
//cluster task complete, all target members gone
|
|
PROCESSOR.SetTaskComplete(t,"SUCCESSFUL");
|
|
}
|
|
}
|
|
}
|
|
|
|
#########################################################################
|
|
## section for creating pincer tasks
|
|
#########################################################################
|
|
if (mCreatePincerTasks == false)
|
|
{
|
|
return tasks;
|
|
}
|
|
Array<WsfAssetPerception> assets = PLATFORM.PerceivedAssets();
|
|
if (assets.Size() < mNumSubsInvolved)
|
|
{
|
|
writeln_d("not enough assets for a pincer maneuver, need ", mNumSubsInvolved);
|
|
return tasks;
|
|
}
|
|
|
|
if (mRequireActiveWeapons)
|
|
{
|
|
int weaponCount = 0;
|
|
foreach(WsfAssetPerception perc in assets)
|
|
{
|
|
WsfPlatform plat = WsfSimulation.FindPlatform(perc.Index());
|
|
weaponCount += plat.WeaponsActiveFor(WsfTrackId());
|
|
}
|
|
if (weaponCount <= 0)
|
|
{
|
|
writeln_d("no active weapons, no pincer yet");
|
|
return tasks;
|
|
}
|
|
}
|
|
|
|
mSubPoint = MeanLocation(assets);
|
|
|
|
#extern bool TestTrackCategory(WsfTrack, string);
|
|
mTrackArray.Clear();
|
|
mTargetNames.Clear();
|
|
Array<WsfTrack> threats = PLATFORM.PerceivedThreats();
|
|
foreach(WsfTrack track in threats)
|
|
{
|
|
if ((track.IFF_Friend()) ||
|
|
(track.SideValid() && track.Side() == PLATFORM.Side()) ||
|
|
(!track. LocationValid()) ||
|
|
(track.SlantRangeTo(mSubPoint) > mThresholdDistance) ||
|
|
(TestTrackCategory(track, "unknown")) )
|
|
{
|
|
continue;
|
|
}
|
|
track.SetBearing( track.Heading() ); #useful for the cluster processing
|
|
mTrackArray.PushBack(track);
|
|
mTargetNames.PushBack(track.TargetName());
|
|
}
|
|
|
|
if (mTrackArray.Size() > 0)
|
|
{
|
|
mClusterManager.UpdateClusters(TIME_NOW,mTrackArray);
|
|
if (mDrawClusters == true)
|
|
{
|
|
for (int i = 0; i < mClusterManager.Count(); i = i + 1 )
|
|
{
|
|
draw_cluster_hull(mClusterManager.Entry(i));
|
|
}
|
|
}
|
|
|
|
if (mClusterManager.Count() > 1)
|
|
{
|
|
//check distances
|
|
double near = MATH.DOUBLE_MAX();
|
|
double near2 = MATH.DOUBLE_MAX();
|
|
|
|
for (int i = 0; i < mClusterManager.Count(); i = i + 1 )
|
|
{
|
|
double dist = mClusterManager.Entry(i).MeanLocation().SlantRangeTo(mSubPoint);
|
|
if (dist < near)
|
|
{
|
|
if (near < near2)
|
|
{
|
|
near2 = near;
|
|
}
|
|
near = dist;
|
|
}
|
|
else if (dist < near2)
|
|
{
|
|
near2 = dist;
|
|
}
|
|
}
|
|
|
|
double ratio = near2/near;
|
|
if (ratio < mMinDistanceRatio)
|
|
{
|
|
writeln_d("threats too spread out! no pincer tasks");
|
|
return tasks;
|
|
}
|
|
}
|
|
|
|
WsfCluster cluster = mClusterManager.Entry(0);
|
|
mMeanPoint = cluster.MeanLocation();
|
|
mMeanBearingValid = cluster.BearingValid();
|
|
mMeanBearing = cluster.Bearing();
|
|
}
|
|
else //no threats to pincer against, exit out
|
|
{
|
|
writeln_d("no threats to pincer against yet!");
|
|
return tasks;
|
|
}
|
|
|
|
//check separation of all subordinates
|
|
//only relavent if not currently in phase 2 (dragging & chasing)
|
|
int dragging = 0;
|
|
# if(mPincerTask.IsValid())
|
|
# {
|
|
# dragging = mPincerTask.AuxDataInt("left_drag") + mPincerTask.AuxDataInt("right_drag");
|
|
# }
|
|
if (dragging == 0)
|
|
{
|
|
double standard = mMeanPoint.TrueBearingTo(mSubPoint);
|
|
double min = 361;
|
|
double max = -361;
|
|
WsfPlatformList subs = PLATFORM.Subordinates();
|
|
foreach(WsfPlatform p in subs)
|
|
{
|
|
double b = MATH.NormalizeAngleMinus180_180(mMeanPoint.TrueBearingTo(p.Location()) - standard);
|
|
min = MATH.Min(min, b);
|
|
max = MATH.Max(max, b);
|
|
}
|
|
double diff = max - min;
|
|
if (diff >= mMaxSeparationAngle)
|
|
{
|
|
writeln_d("pincer complete, targets flanked! no more pincer task!");
|
|
return tasks;
|
|
}
|
|
}
|
|
|
|
|
|
writeln_d("creating pincers tasks! T=", TIME_NOW);
|
|
//first find the group of target(s) we are going to perform the pincer against
|
|
//generate a list of their names and get their mean location
|
|
int N = assets.Size();
|
|
for(int i=0; i<mNumSubsInvolved; i+=1)
|
|
{
|
|
//create a pincer task for this flight group
|
|
//extern double cPincerPriority;
|
|
WsfQuantumTask mPincerTask = WsfQuantumTask.Construct(1.0 * mNumSubsInvolved, "PINCER", mClusterManager.Entry(0).Entry(0));
|
|
mPincerTask.SetAuxData( "ZoneThreatNameArray" , mTargetNames);
|
|
mPincerTask.SetAuxData("ZonePoint", mMeanPoint);
|
|
mPincerTask.SetAuxData("ZoneBearingValid", mMeanBearingValid);
|
|
mPincerTask.SetAuxData("ZoneBearing", mMeanBearing);
|
|
mPincerTask.SetAuxData("all_on_cap", 0);
|
|
|
|
mPincerTask.SetTaskType("PINCER");
|
|
mPincerTask.SetUniqueId((10000000*(i+1)) + mClusterManager.Entry(0).Id());
|
|
|
|
//setup the left & right flyers
|
|
double avgBearing = mMeanPoint.TrueBearingTo(mSubPoint);
|
|
|
|
foreach(WsfAssetPerception asset in assets)
|
|
{
|
|
//Vec3 temp = proc.Platform().LocationWCS();
|
|
double bearing = mMeanPoint.TrueBearingTo(asset.Location());
|
|
double relAngle = MATH.NormalizeAngleMinus180_180(bearing-avgBearing);
|
|
if (mCross)
|
|
{
|
|
relAngle = -1 * relAngle;
|
|
}
|
|
if (relAngle < 0)
|
|
{
|
|
mPincerTask.SetAuxData(asset.Name(), "right");
|
|
}
|
|
else
|
|
{
|
|
mPincerTask.SetAuxData(asset.Name(), "left");
|
|
}
|
|
}
|
|
tasks.PushBack(mPincerTask);
|
|
}
|
|
return tasks;
|
|
end_script
|
|
|
|
|
|
script double FlightLeadEvaluation ( WsfQuantumTask TASK, WsfAssetPerception ASSET)
|
|
#TODO - include missile capability in evaluation
|
|
# for now: just base value on range & whether or not asset as domain capable weapon
|
|
WsfTrack track = PLATFORM.MasterTrackList().FindTrack(TASK.TrackId());
|
|
|
|
writeln_d(PLATFORM.Name(), " evaluating task type ", TASK.TaskType());
|
|
|
|
if (TASK.TaskType() == "WEAPON" && TASK.ResourceIsWeapon() && track.IsValid())
|
|
{
|
|
writeln_d("evaluating weapon task: ", track.TargetName(), ", updated time: ", track.UpdateTime());
|
|
#TODO - select one of the systems?
|
|
for (int i=0; i<ASSET.SystemCount(); i+=1)
|
|
{
|
|
writeln_d("system kind: ", ASSET.SystemKind(i));
|
|
writeln_d("system name: ", ASSET.SystemName(i));
|
|
writeln_d("system quantity: ", ASSET.SystemQuantityRemaining(i));
|
|
writeln_d("system type: ", ASSET.SystemType(i));
|
|
|
|
if (ASSET.SystemKind(i) == "weapon" && ASSET.SystemQuantityRemaining(i) >= 1)
|
|
{
|
|
double value = 1.0 / track.SlantRangeTo(ASSET.Location());
|
|
|
|
struct weaponData = GetWeaponData(ASSET.SystemType(i));
|
|
if (weaponData->type == ASSET.SystemType(i))
|
|
{
|
|
if ((weaponData->domainAir && track.AirDomain()) ||
|
|
(weaponData->domainLand && track.LandDomain()) )
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//check launch computers
|
|
WsfPlatform shooter = WsfSimulation.FindPlatform(ASSET.Index());
|
|
if (shooter.IsValid())
|
|
{
|
|
WsfWeapon weapon = shooter.Weapon(ASSET.SystemName(i));
|
|
if (weapon.IsValid())
|
|
{
|
|
WsfLaunchComputer lcPtr = weapon.LaunchComputer();
|
|
if (lcPtr.IsValid())
|
|
{
|
|
if (track.AirDomain() && lcPtr.IsA_TypeOf("WSF_AIR_TO_AIR_LAUNCH_COMPUTER"))
|
|
{
|
|
return value;
|
|
}
|
|
else if (track.LandDomain() && lcPtr.IsA_TypeOf("WSF_ATG_LAUNCH_COMPUTER"))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
else if (TASK.TaskType() == "PINCER")
|
|
{
|
|
WsfGeoPoint pt = (WsfGeoPoint)TASK.AuxDataObject("ZonePoint");
|
|
double dist = ASSET.Location().GroundRangeTo(pt);
|
|
return 1.0/dist;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
end_script
|
|
|
|
#show_task_messages
|
|
script_debug_writes off
|
|
update_interval 10.0 sec
|
|
asset_representation platform
|
|
reallocation_strategy dynamic
|
|
generator custom FlightLeadTaskGeneration
|
|
evaluator custom FlightLeadEvaluation
|
|
#generator simple_weapon
|
|
#evaluator distance
|
|
allocator optimal_profit
|
|
#allocator_extra_tasks optimal_profit
|
|
allocator_extra_assets optimal_profit
|
|
|
|
end_processor
|
|
|