90 lines
3.9 KiB
C
90 lines
3.9 KiB
C
// Conservative native volume check; no scenery removal or alternate placement.
|
|
class ERR_ConvoyPlacement
|
|
{
|
|
protected bool m_Obstructed;
|
|
protected int m_Visited;
|
|
protected string m_LastReason;
|
|
protected float m_LastLowest;
|
|
protected float m_LastHighest;
|
|
protected IEntity m_FirstObstruction;
|
|
protected float m_LastTrace = -1;
|
|
float LastTraceFraction() { return m_LastTrace; }
|
|
string Diagnostic()
|
|
{
|
|
string prefab = "none";
|
|
vector position;
|
|
if (m_FirstObstruction)
|
|
{
|
|
prefab = "no prefab";
|
|
if (m_FirstObstruction.GetPrefabData()) prefab = m_FirstObstruction.GetPrefabData().GetPrefabName();
|
|
position = m_FirstObstruction.GetOrigin();
|
|
}
|
|
return string.Format("%1; terrainY=%2..%3 span=%4; visited=%5; first=%6 at %7; trace=%8",
|
|
m_LastReason, m_LastLowest, m_LastHighest, m_LastHighest - m_LastLowest, m_Visited, prefab, position, m_LastTrace);
|
|
}
|
|
protected bool Occupancy(IEntity entity)
|
|
{
|
|
m_Visited++;
|
|
if (m_Visited > 128) { m_Obstructed = true; m_LastReason = "native query visit cap"; }
|
|
else if (entity && entity.GetVObject())
|
|
{
|
|
m_Obstructed = true; m_LastReason = "native VObject volume overlap"; m_FirstObstruction = entity;
|
|
}
|
|
return !m_Obstructed;
|
|
}
|
|
static bool Terrain(float origin, float lowest, float highest)
|
|
{
|
|
return ERR_ForceRules.Finite(origin) && ERR_ForceRules.Finite(lowest) && ERR_ForceRules.Finite(highest)
|
|
&& lowest > 0.3 && highest >= lowest && highest - lowest <= 0.75 && origin >= lowest - 0.3 && origin <= highest + 0.3;
|
|
}
|
|
bool Clear(vector position, float heading)
|
|
{
|
|
m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
|
|
m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
|
|
if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
|
|
float radians = heading * Math.DEG2RAD;
|
|
vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
|
|
vector right = Vector(forward[2], 0, -forward[0]);
|
|
float lowest = 1e20, highest = -1e20;
|
|
vector low = position, high = position;
|
|
for (int x = -1; x <= 1; x++)
|
|
{
|
|
for (int z = -1; z <= 1; z++)
|
|
{
|
|
vector sample = ERR_Actors.Ground(position + right * (x * 2.0) + forward * (z * 4.5));
|
|
if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
|
|
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
|
|
{ m_LastReason = "water at footprint sample"; return false; }
|
|
lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
|
|
low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
|
|
high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
|
|
}
|
|
}
|
|
m_LastLowest = lowest; m_LastHighest = highest;
|
|
if (!Terrain(position[1], lowest, highest))
|
|
{ m_LastReason = "terrain origin/height/span check"; return false; }
|
|
low[1] = lowest + 0.05; high[1] = highest + 3.5;
|
|
m_Obstructed = false; m_Visited = 0;
|
|
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
|
|
// Entity bounding boxes can include distant branches or overhead wires.
|
|
// Their overlap is diagnostic only; the filled native volume decides collision.
|
|
vector center = (low + high) * 0.5;
|
|
TraceBox volume = new TraceBox();
|
|
volume.Start = center; volume.End = center + "0 0.01 0";
|
|
volume.Mins = low - center; volume.Maxs = high - center;
|
|
// Nine terrain/water samples already qualify the supporting ground below us.
|
|
volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
|
|
m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
|
|
m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
|
|
m_FirstObstruction = null;
|
|
if (m_Obstructed)
|
|
{
|
|
m_FirstObstruction = volume.TraceEnt;
|
|
m_LastReason = "native filled volume collision";
|
|
if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
|
|
}
|
|
else m_LastReason = "clear";
|
|
return !m_Obstructed;
|
|
}
|
|
}
|