← Home SEEBIE90 DOCS
The SOFLAM Integration Core mod is published under the Arma Public License (APL)Example code is free to useSuggestions: Discord to the author

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.

In plain wordsA guided weapon needs two things: a four-digit code, and a seeker that asks the registry for the nearest laser point with that code.
1. Dependency→2. A code→3. Ask the registry→4. Skip empty points→5. Run on the server→6. Fall back

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:

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:

Example code – free to useMyMod_LaserSeeker.c (your code; pattern from the AH-1Z mod)
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;
	}
}
The zero-vector trapGetClosestTargetByCode(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:

Example code – free to useGTG_AH64_LaserSeekerComponent.c (excerpt)
[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.

Log the reasonThe AH-64 seeker can print one line per quarter second and missile that says which of four states it is in: OHNE CODE (no code was handed over), KEIN ZIEL ZUM CODE (the registry has nothing under this code), AUSSERHALB SICHTFELD (point found, outside the field of view), GEFUEHRT (guided). The line starts with [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.

  1. The entity needs a RplComponent; without one the component cannot send anything.
  2. 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_sSeekerId tells several seekers on one entity apart (AGM, GBU, RKT); find one with GTG_SOFLAM_LaserSeekerComponent.FindSeekerById(entity, "AGM"). m_SeekerOrigin (a point) moves the origin away from the entity origin.
  3. 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);
    }
  4. Read the result each frame: IsLaserTargetFound() first, then GetLaserTargetPosition(), or GetDirectionToLaserTarget() (world angles; the zero vector when the point is beyond the angle limits) or GetSeekerAngles() (angles relative to the entity's own heading, what a turret or a guidance law needs).
  5. Know what “found” means: the seeker accepts a point if it is within m_fRangeLimit. The angle limits are not part of it; ask IsTargetAnglesWithinConstraints() yourself, and only after IsLaserTargetFound() is true.
  6. To choose differently (nearest to the line of sight, moving targets first), derive from the class and override the protected SelectTarget(); override AcceptTarget(target, distance) to add conditions.
Same trapThe component selects the nearest point with 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.

Example code on this page is free to use in your own projects (credit appreciated). The SOFLAM Integration Core mod is published under the Arma Public License (APL). Suggestions: Discord. Arma Reforger / Enfusion are trademarks of Bohemia Interactive.
Impressum · Privacy Policy