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

SOFLAM Integration Core

The designator Changed

One component on whatever emits the laser: a handheld device, a vehicle turret, an aircraft. It finds the point, reports it to the registry and keeps it current. What happens with the point afterwards is none of its business.

In plain wordsThe part that aims the laser, measures the point and reports it. It marks a point, or follows a vehicle, or only measures the distance.
How to hook this up in your own modTo put the designator on your own device, or to report without it, see Report your own designator.

Two modes, as on the real device

Designate measures and reports: seekers lock on. Range only measures and nothing else – no registry entry, no network message, no seeker. Someone who only wants to know how far something is does not give himself away and does not trigger anything.

Both keys are attributes, and an empty field switches that mode off on a prefab. A second device with different needs then edits data and never touches script. The component is GTG_SOFLAM_LaserDesignatorComponent; the one on the SOFLAM prefabs is its subclass GTG_SOFLAM_SoflamDesignatorComponent, which replaces only how the target point is found.

Example code – free to useGTG_SOFLAM_LaserDesignatorComponent.c (excerpt)
class GTG_SOFLAM_LaserDesignatorComponent : ScriptComponent
{
	[Attribute("GTG_SOFLAM_DesignatorAction", UIWidgets.Auto, "Name of the input action that switches the laser")]
	protected string m_sToggleAction;

	[Attribute("true", UIWidgets.Auto, "true = toggle (once on, once off), false = hold")]
	protected bool m_bToggleMode;

	[Attribute("GTG_SOFLAM_RangefindAction", UIWidgets.Auto, "Own key: ONLY measure. Empty = no rangefinder.")]
	protected string m_sRangefindAction;

	[Attribute("true", UIWidgets.Auto, "true = measures while held, false = one measurement per press")]
	protected bool m_bRangefinderContinuous;
	// ...
}

The keys are hooked up and removed in one method with a bool, SetActionsEnabled(bool enable, bool keepLaser = false). The optic calls it when the player starts and stops looking through the device:

Example code – free to useGTG_SOFLAM_LaserDesignatorComponent.c (excerpt, empty-field checks left out)
void SetActionsEnabled(bool enable, bool keepLaser = false)
{
	if (enable == m_bActionsEnabled)
		return;

	InputManager inputManager = GetGame().GetInputManager();
	if (!inputManager)
		return;

	m_bActionsEnabled = enable;

	if (enable)
	{
		inputManager.AddActionListener(m_sToggleAction, EActionTrigger.DOWN, OnToggleAction);
		if (!m_bToggleMode)
			inputManager.AddActionListener(m_sToggleAction, EActionTrigger.UP, OnToggleAction);
		// ... rangefinder and tracking key the same way
	}
	else
	{
		inputManager.RemoveActionListener(m_sToggleAction, EActionTrigger.DOWN, OnToggleAction);
		if (!m_bToggleMode)
			inputManager.RemoveActionListener(m_sToggleAction, EActionTrigger.UP, OnToggleAction);
		// ... the same lines mirrored

		// keepLaser: the device stands on a mount and keeps lasing while the player walks away.
		// Only the keys come off - otherwise the next sight would have two listeners per key.
		if (!keepLaser)
		{
			SetRangefinderActive(false);
			SetLaserActive(false);
		}
	}
}
PitfallRegister and unregister in one method. A pair of separate methods lets a copy-paste turn the second into AddActionListener again, and the listeners then pile up on every cycle.
NoteToggle is the right default for a designator. With hold you cannot hold the key, aim and dial the code at the same time, and a device you put down could never keep lasing.

The attributes

All of these can be set on the prefab. Defaults are the values in the code.

AttributeDefaultMeaning
m_sToggleActionGTG_SOFLAM_DesignatorActionInput action that switches the laser. Empty = no designating.
m_bToggleModetrueToggle, or hold.
m_sRangefindActionGTG_SOFLAM_RangefindActionInput action that only measures. Empty = no rangefinder.
m_bRangefinderContinuoustrueMeasures while held; false = one measurement per press, the value stays.
m_sTrackActionGTG_SOFLAM_TrackingActionInput action for tracking. Empty = no tracking.
m_bTrackAimsHeadtrueA device on a mount pans after the tracked target.
m_bTrackFeedbacktrueShow a message when tracking goes on or off.
m_bTrackVehicleCentretrueAim at the geometric centre of the vehicle, not at the hit point.
m_fMaxDistance4000Longest laser range in metres (the base class's single ray).
m_sDefaultLaserCode1688Laser code at start.
m_fUpdateInterval0.1Seconds between two position reports (0 = every frame).
m_fMinMoveDistance0.5A new report only after the point moved this many metres.
m_sOverlayLayout, m_sOverlayWidgetNameempty, LaserONA layout shown while the laser runs, and the widget in it that is shown and hidden.
m_bDrawDebugTargetfalseDraw the target point as a red sphere.
m_fLaserPowerMin / Max / Step8 / 40 / 1Laser power (LV) limits and step per wheel notch.
m_fSoflamRange (SOFLAM subclass only)3500Longest measuring range in metres.

The code dials

The code is four digits, each 1 to 8 ("1688" by default). On the SOFLAM it is set with four copies of one user action, GTG_SOFLAM_LaserCodeDigitAction, which differ only in an attribute (m_iDigitIndex 0 to 3, start values 1, 6, 8, 8). A device with a different number of wheels gets another copy (and a default code of that length), and no script.

The action does not set the code on the device it sits on. It goes through designator.SetLaserCode(newCode), because that is the one place that distributes a change to the server and everybody else. A wheel that only changed a local field would let the operator turn it while the seeker kept looking for the old code, without any message. The action runs locally only (HasLocalEffectOnlyScript is true), otherwise the engine would replicate the action and the designator would send the change: the digit jumped by two.

A point or a vehicle

Without tracking, the designator marks a point: every tick a fresh trace to wherever the device points. When the vehicle drives off, the mark stays on the ground.

With tracking (key U, GTG_SOFLAM_TrackingAction), remember which entity was hit and where on it – in the entity's own coordinate system. The world point is recomputed from the entity's current transform every time, so the mark travels with the sheet metal instead of with the line of sight.

Example code – free to useGTG_SOFLAM_LaserDesignatorComponent.c (excerpt)
// at lock-on (OnTrackAction), shortened
vector mins, maxs;
ziel.GetBounds(mins, maxs);
vector mitte = (mins + maxs) * 0.5;

// the hit point is the worse choice: it is wherever you happened to hold, say the edge of the roof,
// and it swings wildly when the vehicle turns. The centre of the bounds is stable.
if (vector.Distance(mins, maxs) > 0.5)
	m_vTrackLocal = mitte;

// every tick
protected vector GTG_TrackedWorldPoint()
{
	if (!m_TrackedEntity)
		return vector.Zero;

	vector mat[4];
	m_TrackedEntity.GetWorldTransform(mat);

	return mat[3]
		+ mat[0] * m_vTrackLocal[0]
		+ mat[1] * m_vTrackLocal[1]
		+ mat[2] * m_vTrackLocal[2];
}

Report rarely, but never stop

An early version sent a reliable packet every frame for every active designator. Now the report is throttled to the interval above and sent only when the point really moved – with one exception, a life sign every two seconds, so that a steady laser does not expire in the registry after five seconds. Calculating is separate from sending: while the target moves the point is recomputed every frame (and the mount follows smoothly); only the sending is throttled, so network traffic does not change.

Example code – free to useGTG_SOFLAM_LaserDesignatorComponent.c (excerpt)
// from EOnFrame, shortened
float jetztLz = GetGame().GetWorld().GetWorldTime();
bool lebenszeichen = jetztLz - m_fLetztesLebenszeichen >= 2000;		// life sign every two seconds

if (!lebenszeichen && m_bHasSentPos && vector.DistanceSq(targetPos, m_vLastSentPos)
	< m_fMinMoveDistance * m_fMinMoveDistance)
	return;															// standing still: no traffic

m_fLetztesLebenszeichen = jetztLz;
m_vLastSentPos = targetPos;
m_bHasSentPos  = true;

GTG_SendeZielAktualisieren(targetPos);

All messages are reliable. The unreliable channel has no ordering relative to the reliable one: a position still in flight could arrive after the switch-off and create the point again – a laser that never goes out. The path of a message is described on Who may send.

Limits you measured, not guessed

The laser power is adjustable between two limits (8 and 40 LV, one step per wheel notch with H held). Both were found by driving the device through its range: at the bottom the beam is clearly weak but still there (at zero the player could not tell a broken device from a switched-off laser); at the top the bloom becomes noticeable from about 18 m, and beyond 40 the dot washes out half the picture. The default is not in the script but at the light prefab (GTG_SOFLAM_IRLaserLight.et, LV 25): until someone turns the wheel, the designator's value is −1, “nothing set”, and the prefab value applies. One truth in one place. Once set, the value replicates to everybody, late joiners included, because brightness belongs to the device and not to the viewer.

NoteWhoever changes a limit like this should run it through again. The numbers on these pages are the results of that, not of reasoning.
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