SOFLAM Integration Core
The seeker side Changed
A component on a missile, a turret or a fire-control computer. It does not care who is lasing or whether it is its own laser. It asks the registry and works out direction and angle.
Ask by code, then range
The seeker, GTG_SOFLAM_LaserSeekerComponent, takes the nearest registry point with its code and accepts it if it is within its range limit. Both decisions are protected methods you can override,
so that nobody has to copy the component to choose differently:
//! Which target does this seeker pick? Override this to choose differently - nearest to the line
//! of sight instead of to the origin, moving targets first.
protected GTG_SOFLAM_LaserTarget SelectTarget()
{
GTG_SOFLAM_LaserTargetManager manager = GTG_SOFLAM_LaserTargetManager.GetInstance();
if (!manager)
return null;
return manager.GetClosestTargetByCode(m_sLaserCode, m_vSeekerStart);
}
//! Does the seeker take this target? Default: the range limit only.
//! The ANGLE limits stay out on purpose: a target counts as found even if it is just outside the cone.
protected bool AcceptTarget(GTG_SOFLAM_LaserTarget target, float distance)
{
return m_fRangeLimit <= 0 || distance <= m_fRangeLimit;
}
Whether the target can be flown to is a separate question, asked with IsTargetAnglesWithinConstraints(), or answered by GetDirectionToLaserTarget(true) returning the zero vector outside the limits:
vector GetDirectionToLaserTarget(bool checkConstraints = true)
{
if (!m_bTargetFound)
return vector.Zero;
vector angles = GTG_AnglesTo(m_vTargetPosition);
if (checkConstraints)
{
vector seekerAngles = GTG_ToSeekerAngles(angles);
if (Math.AbsFloat(seekerAngles[0]) > m_fHorizontalAngleLimit
|| Math.AbsFloat(seekerAngles[1]) > m_fVerticalAngleLimit)
return vector.Zero; // found, but outside the cone
}
return angles;
}
| Attribute | Default | Meaning |
|---|---|---|
m_sSeekerId | AGM | Name of this seeker; several on one entity are told apart by it. |
m_sDefaultLaserCode | 1688 | Code at start. |
m_fHorizontalAngleLimit / m_fVerticalAngleLimit | 35 / 35 | Angle limits in degrees, relative to the entity's heading. |
m_fRangeLimit | 40000 | Largest distance in metres at which a target is still accepted (0 or less = unlimited). |
m_fCheckInterval | 0.0333 | Seconds between two checks (0.0333 = 30 Hz). |
m_SeekerOrigin | empty | Origin point of the seeker. Empty = the carrier entity's origin. |
m_bDrawDebug | false | Draw line of sight and direction. |
The public methods: SetSearchActive(bool) (with the shorthands ActivateSearch() and DeactivateSearch()), IsSeekerActive(), IsLaserTargetFound(), GetCode() / SetLaserCode(string),
GetLaserSeekerID(), GetLaserTargetPosition(), GetLaserSeekerOrigin(), GetDistanceToTarget(), GetDirectionToLaserTarget(bool) (world angles),
GetSeekerAngles() (angles relative to the carrier's heading, what a turret or a guidance law needs) and the static FindSeekerById(entity, id).
SetSearchActive(true) sets the frame event. A freshly spawned seeker is off. State and code are replicated (Ask to the server, Do to everybody, RplSave / RplLoad for late joiners), and it warns once if it has to send without owning its entity: [GTG_SOFLAM] Suchkopf AGM sendet ohne Besitz der Traegerentitaet -- der Server verwirft diese Nachrichten. Siehe R1a in ENTWURFSVORGABEN.txt.GetClosestTargetByCode does not skip entries at the zero vector (created, never reported), and neither does this component by default. Override SelectTarget() if that matters for your weapon.Several seekers on one entity
A carrier can have more than one: give each an id (AGM, GBU, RKT) and look it
up by that id with FindSeekerById. Mount, weapon and seeker then stay separate things, and a rocket pod and a missile on the same
turret each keep their own code.
A range limit that is applied
Check that a limit you declare is actually read. The earlier seeker had an attribute for the maximum detection
distance, set to a large number, and no line of code anywhere used it. It looked configurable and was decoration. Here, m_fRangeLimit is applied in AcceptTarget.
Resolve a fixed point once
The earlier seeker called PointInfo.Init() in every tick (30 times a second) to find its emitter point. Now it is called once, in EOnInit, and only if the prefab set an origin; later only the world matrix is read. For a point that is fixed on the model, once at start is enough. For a point on an animated bone it is the other
way round: the cached world transform goes stale, and the point has to be resolved again in every frame you want it
current. Know which of the two you have.