Build independent Everon successor foundation and native regressions
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// All target coordinates come from delivered HQ observations, never a player's
|
||||
// current transform. Road projection stops at an unknown junction/road end.
|
||||
class ERR_MortarPrediction
|
||||
{
|
||||
// Read immediately after a failed synchronous prediction; no extra world scan.
|
||||
static string FailureReason;
|
||||
// Includes observation uplink, HQ downlink and native aiming. All stages
|
||||
// retain the original observation time; radio delivery never renews it.
|
||||
static const float MAX_REPORT_AGE_MS = 60000;
|
||||
|
||||
static bool Fresh(ERR_HQReport report, float now)
|
||||
{
|
||||
return report && report.Target && !report.Distress && now >= report.ObservedAt && now - report.ObservedAt <= MAX_REPORT_AGE_MS && report.TravelMode != "aircraft";
|
||||
}
|
||||
|
||||
static bool Reject(string reason)
|
||||
{
|
||||
FailureReason = reason;
|
||||
return false;
|
||||
}
|
||||
|
||||
static vector ClosestOnSegment(vector point, vector start, vector end)
|
||||
{
|
||||
vector segment = end - start;
|
||||
segment[1] = 0;
|
||||
vector offset = point - start;
|
||||
offset[1] = 0;
|
||||
float lengthSq = segment.LengthSq();
|
||||
if (lengthSq < 0.01)
|
||||
return start;
|
||||
return vector.Lerp(start, end, Math.Clamp(vector.Dot(offset, segment) / lengthSq, 0, 1));
|
||||
}
|
||||
|
||||
static bool FollowRoad(array<vector> points, vector observed, vector velocity, float seconds, out vector impact)
|
||||
{
|
||||
if (!points || points.Count() < 2)
|
||||
return Reject("road polyline has fewer than two points");
|
||||
int segment = -1;
|
||||
float closest = 20;
|
||||
vector projected;
|
||||
for (int i = 0; i < points.Count() - 1; i++)
|
||||
{
|
||||
vector candidate = ClosestOnSegment(observed, points[i], points[i + 1]);
|
||||
float distance = vector.DistanceXZ(candidate, observed);
|
||||
if (distance < closest)
|
||||
{
|
||||
closest = distance;
|
||||
segment = i;
|
||||
projected = candidate;
|
||||
}
|
||||
}
|
||||
if (segment < 0)
|
||||
return Reject("observed vehicle is not within 20m of a road segment");
|
||||
vector forward = points[segment + 1] - points[segment];
|
||||
forward[1] = 0;
|
||||
forward.Normalize();
|
||||
vector travel = velocity;
|
||||
travel[1] = 0;
|
||||
float speed = travel.Length();
|
||||
travel.Normalize();
|
||||
float alignment = vector.Dot(forward, travel);
|
||||
if (Math.AbsFloat(alignment) < 0.7)
|
||||
return Reject("vehicle heading crosses road; alignment below 0.7");
|
||||
int step = 1;
|
||||
int next = segment + 1;
|
||||
if (alignment < 0)
|
||||
{
|
||||
step = -1;
|
||||
next = segment;
|
||||
}
|
||||
float remaining = speed * seconds;
|
||||
vector position = projected;
|
||||
while (next >= 0 && next < points.Count())
|
||||
{
|
||||
float length = vector.DistanceXZ(position, points[next]);
|
||||
if (length >= remaining && length > 0.01)
|
||||
{
|
||||
impact = vector.Lerp(position, points[next], remaining / length);
|
||||
return true;
|
||||
}
|
||||
remaining -= length;
|
||||
position = points[next];
|
||||
next += step;
|
||||
}
|
||||
return Reject("prediction reaches unknown road end/junction");
|
||||
}
|
||||
|
||||
static bool Predict(ERR_HQReport report, float now, float flightSeconds, out vector impact)
|
||||
{
|
||||
FailureReason = "";
|
||||
if (!report) return Reject("target report missing");
|
||||
if (report.Distress) return Reject("distress report cannot authorize fire");
|
||||
if (now < report.ObservedAt) return Reject("observation timestamp is in the future");
|
||||
if (now - report.ObservedAt > MAX_REPORT_AGE_MS) return Reject("observation older than 60s");
|
||||
float seconds = (now - report.ObservedAt) / 1000 + flightSeconds;
|
||||
if (seconds < 0 || seconds > 90)
|
||||
return Reject("observation age plus flight outside 0..90s");
|
||||
if (report.TravelMode == "aircraft") return Reject("aircraft target unsupported");
|
||||
if (report.TravelMode == "mixed travel") return Reject("mixed travel cannot be predicted");
|
||||
impact = report.Position;
|
||||
if (!report.HasVelocity || report.Velocity.Length() < 0.5)
|
||||
return true;
|
||||
if (report.Velocity.Length() * seconds > 1800)
|
||||
return Reject("predicted travel exceeds 1800m");
|
||||
bool vehicle = report.TravelMode == "vehicle" || report.TravelMode == "in vehicle";
|
||||
if (vehicle)
|
||||
{
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager())
|
||||
return Reject("road network unavailable");
|
||||
BaseRoad road;
|
||||
float roadDistance;
|
||||
world.GetRoadNetworkManager().GetClosestRoad(report.Position, road, roadDistance, skipNavlinks: true);
|
||||
if (!road) return Reject("no road near reported vehicle position");
|
||||
if (roadDistance > 20) return Reject("reported vehicle farther than 20m from road");
|
||||
array<vector> points = {};
|
||||
road.GetPoints(points);
|
||||
return FollowRoad(points, report.Position, report.Velocity, seconds, impact);
|
||||
}
|
||||
impact += report.Velocity * seconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ThreatensFriendly(vector impact, vector position, vector velocity, float flightSeconds)
|
||||
{
|
||||
velocity[1] = 0;
|
||||
vector future = position + velocity * Math.Clamp(flightSeconds, 0, 90);
|
||||
// Protect the entire predicted path, not only where a soldier is now.
|
||||
return vector.DistanceXZ(impact, ClosestOnSegment(impact, position, future)) <= 100;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user