← Home SEEBIE90 DOCS
Download PDF
No copies or uploads of M.A.W.S. assetsExample code is free to useNo printed reproduction or modification of this documentationSuggestions: Discord to the authorLicense

Combat

Targeting system

The commander's shoulder launchers never fire blind. A target is set first – a locked vehicle, a locked ground point or a map assignment – and the server checks every step.

In plain wordsThe commander points at something and presses the mark key. The server checks the request, adds the target to a list and waits until it is locked. Rockets only fire at targets from that list.

Flow

Aim ray (client camera)→Mark request (ray + hit)→Server validates ray→Mark list (max 8)→Lock timer→Salvo plan

The aim ray

The ray starts at the camera and skips the own walker: torso, arms and launchers are in the way of almost every view, so every own hit moves the start just past it and traces again.

Example code – free to use
bool AimRay(IEntity walker, IEntity localChar, out vector hit, out IEntity hitVehicle, out bool hasHit)
{
	vector cam[4];
	GetGame().GetWorld().GetCurrentCamera(cam);
	vector dir = cam[2];
	vector from = cam[3];
	vector to = from + dir * m_fRange;		// 2 km, more with a laser module

	array<IEntity> skip = {walker, localChar};
	for (int i = 0; i < 5; i++)
	{
		autoptr TraceParam tp = new TraceParam();
		tp.Start = from;
		tp.End = to;
		tp.Flags = TraceFlags.ENTS | TraceFlags.WORLD | TraceFlags.OCEAN;
		tp.LayerMask = EPhysicsLayerDefs.Projectile;
		tp.ExcludeArray = skip;

		float f = GetGame().GetWorld().TraceMove(tp, null);
		if (f >= 1.0)
			break;

		hit = from + (to - from) * f;
		IEntity e = tp.TraceEnt;
		if (e && e.GetRootParent() == walker)
		{
			from = hit + dir * 0.3;		// own part: continue behind it
			continue;
		}

		hasHit = true;
		if (e)
			hitVehicle = Vehicle.Cast(e.GetRootParent());
		return true;
	}

	hit = to;
	hasHit = false;
	return true;
}

Server-validated marks

The client sends ray start, direction, hit point and the hit vehicle's RplId. The server does not trust the hit point: it must lie close to the ray (5 m + 1 % of the distance), and the ray must start near the walker.

Example code – free to use
bool PointOnRay(vector origin, vector dir, vector point)
{
	vector toPoint = point - origin;
	float along = vector.Dot(toPoint, dir);
	if (along < 0)
		return false;
	float off = (toPoint - dir * along).Length();
	return off <= 5.0 + 0.01 * along;
}

Then the best candidate wins, in this order: the vehicle the ray hit → the best sensor track in a narrow cone → the "next target" cursor → a ground point (needs terrain line of sight, no friendlies close by). Marking the same thing again removes it.

Locks

A marked track is not fireable immediately. Lock time grows with distance (1 s at 500 m to 8 s at 5 km), needs line of sight (unless a datalink module is fitted) and decays after 1.5 s out of sight.

Example code – free to use
float LockTime(float dist)
{
	float t = Math.InverseLerp(500, 5000, dist);
	return Math.Lerp(1.0, 8.0, Math.Clamp(t, 0, 1));
}

Salvo plan

A salvo walks round-robin over all fireable marks, so four rockets at four targets hit each one once. Marks can be assigned to specific weapons from the map (a bitmask per mark); a salvo of weapon X skips marks assigned elsewhere.

Example code – free to use
int Rotate(array<ref MySalvoTarget> plan)
{
	int start = m_iPlanOffset % plan.Count();
	for (int i = 0; i < start; i++)
	{
		plan.Insert(plan[0]);
		plan.RemoveOrdered(0);
	}
	m_iPlanOffset += m_iRocketsThisSalvo;
	return start;
}
PitfallIf there are marks but none of them is fireable, refuse the salvo. Silently falling back to "fire at the crosshair" once sent rockets into the sky.

The commander lock cross

Since players fired without a target, the commander now sees a small cross in the screen centre. Below it the HUD says what the mark key would lock there – LOCK VEHICLE, LOCK POINT TRACK or NO TARGET – followed by the key the player has bound. The binding is read at runtime, so rebinding is reflected immediately.

Example code – free to use
string BoundKey(string action)
{
	InputManager im = GetGame().GetInputManager();
	EInputDeviceType dev = EInputDeviceType.KEYBOARD;
	if (im.GetLastUsedInputDevice() == EInputDeviceType.GAMEPAD)
		dev = EInputDeviceType.GAMEPAD;

	InputBinding binding = im.CreateUserBinding();
	array<string> list = {};
	if (!binding.GetBindings(action, list, dev) || list.IsEmpty())
		return string.Empty;

	return PrettyKey(list[0]);		// "keyboard:KC_I" -> "I", "gamepad0:shoulder_left+gamepad0:a" -> "LB + A"
}
Example code on this site is free to use in your own projects (credit appreciated). The M.A.W.S. mods, their code and assets are covered by the M.A.W.S. License – no copies or uploads. This documentation may not be reproduced in print or modified. Suggestions: Discord. Arma Reforger / Enfusion are trademarks of Bohemia Interactive.
Impressum · Privacy Policy