SOFLAM Integration Core
Integrating a helicopter or aircraft Changed
Adding laser guidance to an aircraft is mostly not about the laser. It is about racks that do not launch, seats that are called something else, keys that were renamed, and state that only one machine knows. This is the list of things that went wrong in the AH-64 and AH-1Z mods, in the order you will meet them.
GTG_AH64_*); the base classes WCS_Armament_* belong to WCS Armaments, not to the Core.What an aircraft needs
Five small pieces, each with one job. None of them knows the designator: they only know the registry and a code. All of them are built by the aircraft mod; the Core provides only the registry.
| Piece | Sits on | Job | Class in the AH-64 mod |
|---|---|---|---|
| Code holder | The turret | Holds the aircraft's four-digit code, replicates it on every change and hands it to late joiners | GTG_AH64_LaserCodeComponent |
| Cockpit code dials | Four user actions, positions on the vehicle | Let the crew change the code; the action and the holder must be findable from each other | GTG_AH64_CockpitLaserTuneAction (m_iLaserCodeDigit 0..3) |
| Seeker | The missile | Asks the registry for the nearest target with the aircraft's code | GTG_AH64_LaserSeekerComponent, a subclass of WCS_Armament_LaserSeekerComponent |
| Flight computer | The missile | Owns the attack modes and flies them (four: DIR, LOBL, LO, HI) | GTG_AH64_LaserFlightComputerComponent |
| Code injector | The turret | Copies the code onto every missile as it leaves the rack | GTG_AH64_LaserCodeInjectorComponent |
GTG_AH64_ and GTG_AH1Z_.Weapon racks
The upper row that never leaves the rail
A missile pod can carry its rounds in two rows. If the upper row sits so close to the pod body that the engine refuses to apply the launch impulse, those missiles leave the rail carrying only the aircraft's own speed and fall. The signature is unmistakable: of sixteen missiles exactly eight drop, and it is always the same eight, because the launcher serves the upper row of every pod first.
The fix is one attribute on the pod's weapon component: push the missile clear before the engine launches it. Measure it. The AH-64 flight computer writes the start state of each missile once (including the velocity vector, which tells “never launched” from “launched and stopped” – its length alone cannot).
WCS_Armament_WeaponComponent "{629D11C992611C79}" {
m_fLaunchOffset 2 // the base value in Pod_M299.et is 0 and leaves the upper row without impulse
}
| Upper row | Lower row | |
|---|---|---|
| Launch speed before | 1.6 - 2.1 m/s, then free fall | 77 - 93 m/s |
| Launch speed after | 78 - 94 m/s | 78 - 94 m/s |
The AH-1Z mod's own pod prefab (Pod_M299_AH1Z.et) sets the same value, with a comment that exactly half of the 16 missiles fall off without it. If your pod is derived from the stock M299, check it.
How high the tube sits
On a helicopter standing on the ground the tubes are under a metre above the terrain (0.9 m on the AH-64). A flat attack mode then grazes the ground in front of the aircraft. That is geometry, not a fault in the rack, and it does not occur in flight. Test from the air before you chase it.
Override or new variant
The first AH-64 mod built a complete new vehicle variant. Its code dials and the third munition choice then did not appear on the Apache that was already in the test world. The fix was to make the turret and pod prefabs claim the same resource identity (GUID, through the .et.meta file) as the WCS originals:
the unchanged vehicle prefab already refers to them by GUID, and that reference now points at the mod's version. Read the cost, too: such an override has to be compared with the original at every update of the base mod; the AH-64 mod keeps a copy of the original turret base under its own GUID for that reason.
Seats and turret controls
The seat has a name
A component that registers a key only while the local player sits in a named seat has to be told the right name.
It differs between aircraft: in the AH-64 the weapon seat is gunner, in the AH-1Z copilot. Read it from
the vehicle prefab, do not assume. And do not confuse the compartment name with the interaction context a user action hangs on (step 5).
Do not keep a second counter
The first version counted every press of the mode key to know which attack mode was selected. It drifted: WCS only switches the mode while the missile weapon is selected, so every press with the rocket pod or the cannon in hand moved the private count one place ahead of the screen. The report was “HI flies like DIR, LO flies highest” – everything shifted by one. Read the mode WCS itself keeps; it is public and the server's copy is kept up to date.
int GetCurrentIndex()
{
IEntity owner = GetOwner();
if (owner)
{
WCS_Armament_LoadoutComponent loadout = WCS_Armament_LoadoutComponent.Cast(owner.FindComponent(WCS_Armament_LoadoutComponent));
if (loadout)
return loadout.GetDeliveryModeIndex(); // the mode WCS shows on the screen = the mode that has to fly
}
return m_iCurrentIndex; // own counter only as a fallback
}
Input actions get renamed
WCS_Armaments 8.3.0 routes its own action name for cycling the attack mode to the vanilla turret action TurretWeaponNextFireMode and registers only that. A listener on
the old name stayed silent: the screen switched modes, the server kept flying the first one, and nothing was logged.
Resolve the name through the helper WCS uses itself, so the next change does not break you again.
m_InputManager.AddActionListener(WCS_Armament_InputBindings.Action("WCS_Armament_CycleWeaponFireMode"), EActionTrigger.DOWN, OnCycle);
override of a method that no longer exists does not just fail your mod: it takes the whole script module down for everybody.Where the code runs
The key is handled on the client, the missile flies on the server. Anything the flight logic needs – the code, the mode – has to be replicated or sent to the server (the AH-64 mod's mode sync sends from client to server). A purely local variable is invisible to it, and in Workbench you will not notice, because client and server are the same process there.
The same split hides a second trap: on a client that does not own the aircraft, the frame events of a vehicle component never run. Anything that has to move every frame on every machine needs its own entity, or a call queue (the AH-64 mod's missile camera is such an entity; its seeker-camera component keeps a call-queue clock as a replacement).
The laser code
- Replicate it on every change and hand it to late joiners. A code that is only sent when something is toggled leaves a pilot reading a stale number on his screen.
- The seeker takes the nearest target with its code. Two lasers with the same code, one of them next to the aircraft, send every missile to the nearer one. Give a test emitter its own code (the AH-64 and AH-1Z mods ship a test emitter that publishes a fake SOFLAM into the registry), and remove always-on test devices from the test world.
- Show the code where the crew looks. A text overlay follows the head; the AH-64 mod moved the code onto a cockpit screen instead.
The missile in flight
A phantom explosion
WCS remembers the last terrain contact of a missile and, when the fuse fires, uses it as the explosion position without checking how old it is. A lofted missile that brushes a hillside on the way down then shows its explosion where it touched, up to 215 m before the warhead really detonates (measured: contact at 2315/98/2353, detonation 0.45 s later at 2507/150/2265). The fix is a guard on the method that publishes the impact: if the remembered contact is far from the missile, use the missile.
modded class WCS_Armament_MissileMovementComponent
{
override protected void PublishImpact(IEntity hitEntity, vector position, vector normal, ResourceName effectOverride, bool useRecordedContact = false)
{
// At about 500 m/s a missile covers up to 8 m per frame; a real contact is far closer than 25 m.
if (useRecordedContact && m_Projectile && GTG_IsOurHellfire() && vector.Distance(position, m_Projectile.GetOrigin()) > 25)
{
super.PublishImpact(null, m_Projectile.GetOrigin(), vector.Up, effectOverride, false);
return;
}
super.PublishImpact(hitEntity, position, normal, effectOverride, useRecordedContact);
}
}
GTG_IsOurHellfire()). The class is the base of every missile in WCS.A camera that follows the missile
A missile camera has to be its own entity, because of the ownership rule above. Update it right after the missile
has moved in the same frame. Updated before, it trails: in the dedicated-server tests the camera hung 10 to 28 m behind the missile (one run measured 10 - 19 m, another 17 - 28 m) instead of one metre in front of it.
The AH-64 camera is a ScriptCamera subclass that sets its world matrix in its own post-frame event, after the movement, and then lets the base class pass exactly that fresh matrix to the engine; there is no order race with the vehicle component.
class GTG_AH64_MissileCamera : ScriptCamera
{
protected IEntity m_Ziel; // the missile
protected vector m_vLokalPosition; // where on it the camera sits
override protected void EOnPostFrame(IEntity owner, float timeSlice)
{
if (m_Ziel)
{
// ... set this camera's world matrix from the missile's CURRENT world matrix ...
}
super.EOnPostFrame(owner, timeSlice); // the base hands this matrix to the engine
}
}
Testing it
| Check | Why |
|---|---|
| Run a real dedicated server and a client | Workbench hides ownership, replication and the key-versus-flight split |
| Fire from the air, at 30 m or more | A parked helicopter adds a ground-contact effect that has nothing to do with your work |
| Fire every mode from every rack row, left and right | Rack faults show as a pattern: half the rounds, or one side |
| Compare the peak height of each mode | The modes must differ in the order the screen shows them |
| Press the mode key with another weapon in hand, then select the missile again | Catches any private counter that drifts from the screen |
| Use a unique code, and no other laser nearby | The nearest matching target wins |
| Tag every log line, write the role of the machine into it, then switch the logging off | A switch left on fills the log with thousands of lines (one HUD component once wrote about 500 lines a second) |
Working examples
Two aircraft that use these pieces on the Workshop:
- SagesAH1Z – SOFLAM Integration – the AH-1Z by Sage, with laser guidance on its copilot seat.
- AH-64 SOFLAME Integration – the AH-64D with missile camera, TADS screen and attack modes.
Both list the SOFLAM Integration Core as a dependency.