SOFLAM Integration Core
Night and thermal vision Changed
An infrared laser should be visible to everybody who has night vision, whatever the operator selected. And a sight with night and thermal modes needs to put its filter on the right camera – and take it off the same one.
GTG_SOFLAM_VisionModeComponent to the device and call SetInputEnabled(true) when the player starts looking through it and false when they stop. If you have your own night-vision optic, call GTG_SOFLAM_NVState.SetTemporaryNVState(true) while it is on, and infrared lights become visible for that viewer.Infrared visibility is a per-viewer fact
Whether this client can currently see infrared light is a purely local state. A real IR laser is visible to
anyone with their own night vision, independent of what the operator of the device selected. So there is nothing to
replicate: each machine keeps one flag, set when the player looks through a night-vision optic. It is a static field of GTG_SOFLAM_NVState.
class GTG_SOFLAM_NVState
{
protected static bool s_bTemporaryNV;
// set by devices: true = this client is looking through a night-vision optic and can see IR light
static void SetTemporaryNVState(bool state)
{
s_bTemporaryNV = state;
}
static bool IsTemporaryNVState() { return s_bTemporaryNV; }
static bool CanLocalPlayerSeeIR() { ... } // below
}
SetNightVision it used to be set at the end, behind the check “is there a renderer”. Whenever the method left early, the flag kept its old value, and that player saw every IR light without goggles, day and night. The beam of the test tripod showed as a red line to the naked eye. The flag is pure state and has nothing to do with drawing; it is set first, before any early return.An optional mod without a compile dependency
If the viewer wears the night-vision goggles of another mod, that should count too – but a compile reference
to that mod would make yours unbuildable without it. The Core calls it by name at runtime (RHS-StatusQuo's RHS_IsNVOff on the player controller): if the mod is loaded the call returns
the real answer, if not it fails and only your own state counts.
static bool CanLocalPlayerSeeIR()
{
if (s_bTemporaryNV)
return true;
SCR_PlayerController playerController = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!playerController)
return false;
ScriptModule scriptModule = GetGame().GetScriptModule();
if (!scriptModule)
return false;
bool nvOff = true;
if (scriptModule.Call(playerController, "RHS_IsNVOff", false, nvOff)) // false if there is no such method
return !nvOff;
return false;
}
The light itself, GTG_SOFLAM_LightEntity, has the gate built in: SetEnabledWithIRCheck(true) only switches an IR light on (prefab attribute m_bIsIR) if CanLocalPlayerSeeIR() says so.
Which modes exist is data
Two attributes of GTG_SOFLAM_VisionModeComponent, m_bAllowNightVision and m_bAllowThermal (both default true), decide which modes a device has at all, and the cycle is built from them: both on gives
normal → night → thermal, night only gives normal → night, none switches the whole component off (it hooks up no keys). An older
device without thermal needs a tick in the prefab editor, not a class. The mode is the enum GTG_SOFLAM_EVisionMode (NORMAL, NIGHT_VISION, THERMAL); GetOnVisionModeChanged() returns a ScriptInvoker that fires with the component and the new mode, so an optic can update its display without asking every frame.
A mode that is switched off on the prefab is quietly taken back to normal when a script asks for it.
The camera-id trap
The thermal filter hangs on a camera index. Remember the index in use when you switch the filter on, and
clear that one when you switch it off. GetCurrentCameraId() can have changed in between – the
player left the optic quickly, or changed zoom, which in some sights is a different camera. Clearing on the wrong id
left the thermal filter on the active camera and the screen stayed white until switching again released it.
protected void SetThermal(bool state)
{
if (!GTG_HasRendering())
return;
BaseWorld world = GetGame().GetWorld();
if (!world)
return;
if (state)
{
m_iThermalCamId = world.GetCurrentCameraId(); // REMEMBER it
world.SetCameraPostProcessEffect(m_iThermalCamId, m_iThermalSlot,
PostProcessEffectType.ThermalImaging, GTG_CurrentThermalFilter());
return;
}
int camId = m_iThermalCamId;
if (camId == -1)
camId = world.GetCurrentCameraId();
// clear THE camera we set it on...
world.SetCameraPostProcessEffect(camId, m_iThermalSlot, PostProcessEffectType.ThermalImaging, string.Empty);
// ...and, to be safe, the current one too if it differs. Costs nothing where nothing was set.
int currentCamId = world.GetCurrentCameraId();
if (currentCamId != camId)
world.SetCameraPostProcessEffect(currentCamId, m_iThermalSlot, PostProcessEffectType.ThermalImaging, string.Empty);
m_iThermalCamId = -1;
}
Night vision uses a vanilla HDR slot. The Core adds one method to the vanilla SCR_ScreenEffectsManager (modded class), GTG_SOFLAM_SetHDR(ResourceName material, bool enable), writing slot/priority 16 (the same as RHS, so that
both together do not stack two effects). An empty material clears the slot instead of putting the vanilla HDR on it: it used to set the vanilla material without the parameters the game runs it with, the slot stayed occupied, and in daylight the whole picture burned out after leaving night vision.
Smaller measured choices
| Choice | Reason |
|---|---|
Own copies of the thermal filters (GTG_ThermalFilter_WhiteAsHot, _BlackAsHot) with a fixed min/max contrast (AutomaticMinMax) | The stock filter (RHS's AutomaticHistogramPLHE) recomputes its contrast curve periodically, and that shows as short, irregular dropouts of the picture. |
Thermal slot m_iThermalSlot = 12 | RHS's vehicle thermal uses slot 11. Two effects on the same slot overwrite each other. Pick a number others do not, and note it down. |
| Night-vision gain levels 0.01 … 30, start on index 5 (= 1.0) | Taken from the AH-64 mod, where they were flown in. |
Mode key J (GTG_SOFLAM_VisionModeAction), not N | In a collection of about 70 mods N did not reach the action at all, not even with shift. Measured in a multiplayer test. |
Other keys: GTG_SOFLAM_ToggleNvThermalAction (night ↔ thermal), _GainUpAction / _GainDownAction (LSHIFT + wheel: night-vision gain or thermal filter), _LaserPowerUpAction / _LaserPowerDownAction (H + wheel) | All six are attributes of the component, so another device can use other action names. |
Skip everything visual on the dedicated server (RplSession.Mode() != RplMode.Dedicated) | No render engine, no point doing material work. |