For other modders
2. Make a weapon follow the laser New
Your weapon does not need to know who is lasing. It needs a code and the nearest point with that code. There are two ways to get it; the first is the one every aircraft mod uses today.
Do step 1 first. The principle is on the registry page: the match is by code only.
Route A – read the registry from your own seeker (proven)
The AH-64 and the AH-1Z both do this. The AH-64 and AH-1Z seekers extend the seeker of WCS Armaments
(WCS_Armament_LaserSeekerComponent) and replace its target position. Your weapon's own
seeker does the same job, whatever base class it has.
Step 2 – Give the weapon a code
The code is a string of four digits, each 1 to 8, for example "1688" (the default everywhere). The comparison is an exact string
comparison, and an empty code never matches anything. Where the code comes from depends on the weapon:
- A launcher with a fixed code: an attribute on your launcher prefab with
"1688"as the default, as the Core's own seeker has (m_sDefaultLaserCode). - A weapon fired from an aircraft or turret: the code is copied onto the missile when it leaves the rail – see the injector on the page about aircraft.
Step 3 – Ask the registry, skip empty points
This is the heart of every integration. It is the AH-1Z logic, cut to the point:
class MyMod_LaserSeeker : ScriptComponent
{
protected string m_sTargetCode;
void SetTargetCode(string code)
{
m_sTargetCode = code;
}
// true and the point, if somebody is lasing under our code
bool FindLaserPoint(out vector point)
{
point = vector.Zero;
if (m_sTargetCode.IsEmpty())
return false;
// ask EVERY time and check for null: no world loaded = no registry. Never keep it in a field.
GTG_SOFLAM_LaserTargetManager manager = GTG_SOFLAM_LaserTargetManager.GetInstance();
if (!manager)
return false;
array<GTG_SOFLAM_LaserTarget> targets = {};
if (manager.GetTargetsByCode(m_sTargetCode, targets) == 0)
return false;
float bestDistSq = -1;
foreach (GTG_SOFLAM_LaserTarget target : targets)
{
if (!target)
continue;
vector position = target.GetPosition();
if (position == vector.Zero)
continue; // created but never reported: CreateTarget stores no position
float distSq = vector.DistanceSq(GetOwner().GetOrigin(), position);
if (bestDistSq < 0 || distSq < bestDistSq)
{
bestDistSq = distSq;
point = position;
}
}
return bestDistSq >= 0;
}
}
GetClosestTargetByCode(code, fromPosition) exists, but it does not skip entries at the zero vector. A point that was created and not yet reported would win if it is the nearest, and a missile would turn towards the world origin for a moment. The AH-64 and AH-1Z seekers loop over GetTargetsByCode instead.Step 4 – Decide what “in view” means for your weapon
The registry knows nothing about your seeker's field of view. If you want one, apply it yourself. The AH-64 seeker compares the angle between the
missile's forward axis and the direction to the point with m_fLaserAcquisitionFov. Its ammo prefab sets that to 180, on purpose:
the stock default of 70 degrees made the hit rate depend on the direction of the shot relative to the target, because the target comes from a code match
and not from real optics.
Step 5 – Run it where the missile flies, and tell the clients
A missile is simulated on the server. The AH-64 seeker returns at once on a proxy (if (m_RplComponent && m_RplComponent.IsProxy()) return;).
That has a consequence: anything the seeker computes exists only on the server. The AH-64 mod showed a green “guided” marker on a
client that never appeared on a dedicated server, because the flag was only set there. The fix was one attribute – a replicated property:
[RplProp()]
protected bool m_bGTG_ZielGefuehrt; // "target is being guided"; without [RplProp] only the server ever knows
Step 6 – Say what happens when there is no point
If the registry has no point for your code, do something sensible. The AH-64 seeker falls back to the behaviour of its base class (the gunner's sight direction) and carries on. Without a fallback, a missile fired without a laser simply falls out of the sky.
[GTG_AH64][TELEMETRIE] and is off by default (GTG_LOG_TELEMETRIE). Build the same into your own seeker; each state points at a different place.Route B – attach the Core's seeker component
The Core has a ready-made seeker, GTG_SOFLAM_LaserSeekerComponent. It does the registry query, the distance limit and the angle calculation for you
and replicates its on/off state and code. Treat this route as less proven: no prefab in the Core and none of the mods checked attaches it.
- The entity needs a
RplComponent; without one the component cannot send anything. - Add the component. These are its attributes with their defaults:
Example code – free to useprefab text; values are the defaults
GTG_SOFLAM_LaserSeekerComponent "{<new GUID>}" { m_sSeekerId "AGM" m_sDefaultLaserCode "1688" m_fHorizontalAngleLimit 35 m_fVerticalAngleLimit 35 m_fRangeLimit 40000 m_fCheckInterval 0.0333 }m_sSeekerIdtells several seekers on one entity apart (AGM,GBU,RKT); find one withGTG_SOFLAM_LaserSeekerComponent.FindSeekerById(entity, "AGM").m_SeekerOrigin(a point) moves the origin away from the entity origin. - Switch it on. The search loop only runs while the seeker is active; at spawn it is off. At launch call
Example code – free to useyour launch code
GTG_SOFLAM_LaserSeekerComponent seeker = GTG_SOFLAM_LaserSeekerComponent.FindSeekerById(missile, "AGM"); if (seeker) { seeker.SetLaserCode(code); // ignored if empty or unchanged seeker.SetSearchActive(true); } - Read the result each frame:
IsLaserTargetFound()first, thenGetLaserTargetPosition(), orGetDirectionToLaserTarget()(world angles; the zero vector when the point is beyond the angle limits) orGetSeekerAngles()(angles relative to the entity's own heading, what a turret or a guidance law needs). - Know what “found” means: the seeker accepts a point if it is within
m_fRangeLimit. The angle limits are not part of it; askIsTargetAnglesWithinConstraints()yourself, and only afterIsLaserTargetFound()is true. - To choose differently (nearest to the line of sight, moving targets first), derive from the class and override the protected
SelectTarget(); overrideAcceptTarget(target, distance)to add conditions.
GetClosestTargetByCode, so it inherits the zero-vector trap above. If your missile could be fired in the first tenth of a second after a laser is switched on, override SelectTarget() and skip the zero vector.More on this component, the on/off replication and the origin point: The seeker side.