← 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

4. Report your own designator New

Anything that can point at the ground can be a designator: it creates one registry entry, keeps its position fresh, and removes it. The hard part is not the three calls. It is getting them from the player's machine to everybody else's.

In plain wordsA drone, a turret or any other device that should mark a target puts its laser point into the registry. Either with the Core's designator component, or on its own through a relay on the player's character.
1. Pick a creator id→2. Create / update / remove→3. Send it via the server→4. Keep it alive→5. Clean up

Do step 1 first.

Which way: the Core's designator, or your own?

Core designator componentYour own, calling the registry
What it isGTG_SOFLAM_LaserDesignatorComponent on your device: keys, rangefinder, tracking, laser power, relay, replication all included.Three registry calls and a relay you write.
FitsA device with an optic that a player looks through, like the SOFLAM.Anything else: a drone camera, a turret.
Proven inThe SOFLAM prefabs of the Core.The test emitters of the AH-64 and AH-1Z mods (without a relay).
NeedsA RplComponent on the device; the target position comes from the camera (or, in the SOFLAM subclass, from the optic).Any entity with a RplComponent as creator id.

Way 1 – the Core's designator component

  1. The device needs a RplComponent. Without it the component prints [GTG_SOFLAM] Designator ohne RplComponent an der Traegerentitaet -- ohne die gibt es keine Ersteller-Id und keine Replikation. once and stays quiet.
  2. Add GTG_SOFLAM_LaserDesignatorComponent (or inherit a SOFLAM prefab). The defaults give you the keys T (designate), Y (range only) and U (track), code "1688", 10 reports per second at most. The attributes are on the designator page.
  3. Tell it when the player operates the device: designator.SetActionsEnabled(true) hooks the keys up, SetActionsEnabled(false) removes them (and switches the laser off, unless you pass true as the second argument). The SOFLAM's optic calls these when the player starts and stops looking through it.
  4. Tell it where the camera is, if it is not the player's: SetCameraEntity(entity). Without one, it measures from the active game camera.
  5. For a different beam geometry, derive and override ComputeTargetPosition() – and ask GTG_TrackingPoint() first, as the Core's own two versions do, or tracking silently stops working in yours.
Derived classesThe Core finds its designator with GTG_SOFLAM_LaserDesignatorComponent.FindOn(entity), which looks for the base class and then for GTG_SOFLAM_SoflamDesignatorComponent. A search for the base class alone does not find a derived class on the SOFLAM. Three symptoms – code wheel stuck, laser indicator stuck on “on”, designate key does nothing – had one cause, and none of them printed anything. If you derive your own, FindOn will not know it: call your own search, or make sure the code that needs it finds yours.

The Core's designator has no shipped example outside the SOFLAM prefabs. Test it on a dedicated server before you rely on it for a vehicle.

Way 2 – call the registry yourself

A designator that is not the SOFLAM reports into the registry with three calls on GTG_SOFLAM_LaserTargetManager. Everything else is networking.

Step 1 – Pick the creator id

The registry keys each entry by a RplId: the replication id of the entity that creates the laser point. One creator has exactly one entry; creating again replaces it. The Core's designator uses the id of its own device (GetCreatorId() returns RplComponent.Id()). A drone would use the drone's id. It must be valid (creatorId.IsValid()), otherwise CreateTarget and UpdateTarget do nothing. On an entity that exists in the world file from the start the id may not be valid yet in EOnInit – the AH-64 test emitter waits for the first POSTFRAME.

Step 2 – Three calls

Example code – free to usethe three calls the Core's designator makes too
GTG_SOFLAM_LaserTargetManager manager = GTG_SOFLAM_LaserTargetManager.GetInstance();
if (!manager)
	return;								// no world: nothing to do, ask again next time

manager.CreateTarget(creatorId, code);	// the entry exists, but with NO position yet
manager.UpdateTarget(creatorId, position, code);	// position and code; creates the entry if it is missing
manager.RemoveTarget(creatorId);

Call UpdateTarget straight away; it creates the entry when there is none (an update can even reach a late-joining client before the create). A point that was only created sits at the zero vector and every reader has to skip it. Mind the order of the arguments: UpdateTarget(creatorId, position, code), not code first.

Step 3 – Send it through the server, from a place the player owns

The registry replicates nothing. Every machine fills its own copy, from a broadcast. So the three calls must run on every machine, and the way there is: the player's machine asks the server, the server applies it itself and broadcasts. And a client may only call a server RPC on something it owns. The Core's relay on SCR_CharacterControllerComponent is the player's own character – but it only resolves the id to a GTG_SOFLAM_LaserDesignatorComponent, so a drone cannot use it. You need a second one with your own names:

Example code – free to usepattern (Create and Remove work the same way)
modded class SCR_CharacterControllerComponent
{
	// the server: apply here, then tell everybody else
	void MyMod_ServerUpdate(RplId creator, string code, vector position)
	{
		MyMod_ApplyUpdate(creator, code, position);
		Rpc(MyMod_DoUpdate, creator, code, position);
	}

	protected void MyMod_ApplyUpdate(RplId creator, string code, vector position)
	{
		GTG_SOFLAM_LaserTargetManager manager = GTG_SOFLAM_LaserTargetManager.GetInstance();
		if (manager)
			manager.UpdateTarget(creator, position, code);
	}

	[RplRpc(RplChannel.Reliable, RplRcver.Server)]
	void MyMod_AskUpdate(RplId creator, string code, vector position)
	{
		MyMod_ServerUpdate(creator, code, position);
	}

	[RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
	void MyMod_DoUpdate(RplId creator, string code, vector position)
	{
		MyMod_ApplyUpdate(creator, code, position);
	}

	// Rpc() is protected: the call must stand INSIDE this class, so callers use this public method
	void MyMod_SendUpdate(RplId creator, string code, vector position)
	{
		Rpc(MyMod_AskUpdate, creator, code, position);
	}
}

And the sender, which the player's machine runs:

Example code – free to usethe sender; Replication.IsServer() decides the route
static SCR_CharacterControllerComponent MyMod_Sender()
{
	PlayerController controller = GetGame().GetPlayerController();
	if (!controller)
		return null;					// dedicated server: no local player

	IEntity character = controller.GetControlledEntity();
	if (!character)
		return null;

	return SCR_CharacterControllerComponent.Cast(
		character.FindComponent(SCR_CharacterControllerComponent));
}

// ...
SCR_CharacterControllerComponent relay = MyMod_Sender();
if (relay)
{
	if (Replication.IsServer())
		relay.MyMod_ServerUpdate(creatorId, code, position);	// a broadcast does not deliver to its own sender
	else
		relay.MyMod_SendUpdate(creatorId, code, position);
}

Step 4 – Keep it alive, but not too hard

The registry throws away any entry that has not been updated for 5 seconds (checked once a second). So you must keep reporting, whether the point moves or not. The Core's designator reports:

Sending every frame also works, but on a reliable channel it is a lot of traffic. Take the Core's rhythm unless you have a reason.

When the operator can no longer see the target (looks away), keep sending the last valid point rather than stopping, so that clients who only come into range later still get a position.

Step 5 – Clean up

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