← 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

Torso & aim stabilization

The legs bounce, the torso must not. And the gunner's sight should stay on the target while the walker walks and turns – without ever fighting the mouse.

In plain wordsThe upper body ignores the bouncing of the hips. The gunner's sight remembers the point it was aimed at and keeps pointing there while the walker moves - until the player moves the mouse.

Torso: keep the slow part, drop the fast part

The torso hangs on the pelvis, but only on its posture (crouch, slope levelling, slow settling). Bob, sway, roll and lean are taken out. This runs in the same frame as the leg rig, right after the pelvis – a frame of lag would show as jitter.

Example code – free to use
void ApplyUpperBody(MyLegRig rig, MyPartRegistry reg, float dt)
{
	vector posture[4];
	rig.GetPelvisPostureLocal(posture);		// crouch + slope, no bob/sway/roll

	vector settle[4];
	rig.GetPelvisSettleLocal(settle);	// slow height adaption: follow it, or the waist opens a gap
	vector torsoBase[4];
	Math3D.MatrixMultiply4(posture, settle, torsoBase);

	vector yaw[4];
	Math3D.AnglesToMatrix(Vector(m_fTorsoYaw, 0, 0), yaw);
	yaw[3] = m_vTorsoPivot - rig.GetPelvisRestOrigin();

	vector torso[4];
	Math3D.MatrixMultiply4(torsoBase, yaw, torso);
	reg.SetTarget(PART_TORSO, torso);
	reg.Write(PART_TORSO);
}

Fast arms, slow torso

A 20 t torso that snaps to the mouse looks wrong. The arms take small and fast corrections inside a ±35° window; the torso follows lazily (gain × remaining angle, max 80°/s, 3° dead band).

Example code – free to use
float FollowTorso(float aimYaw, float dt)
{
	float err = MyMath.WrapDeg(aimYaw - m_fTorsoYaw);
	float over = Math.Max(Math.AbsFloat(err) - m_fTorsoDeadband, 0);
	float speed = Math.Min(m_fTorsoMaxSpeed, m_fTorsoGain * over);
	m_fTorsoYaw += Math.Clamp(err, -speed * dt, speed * dt);

	// never let the aim leave the arm window
	m_fTorsoYaw = aimYaw - Math.Clamp(MyMath.WrapDeg(aimYaw - m_fTorsoYaw), -m_fArmWindow, m_fArmWindow);
	return m_fTorsoYaw;
}

Convergence on the measured aim point

The two arm weapons sit 6 m apart. Instead of a fixed convergence distance, a ray from the gunner's eye along the sight measures the real distance (every 0.1 s, smoothed in log space so 20→40 m feels like 500→1000 m) and each barrel line is aimed exactly through that point.

The aim point lock

The engine has no turret stabilization API, and simply re-applying the old direction every frame overwrote the player's input – the turret could not be turned while walking. The working solution locks a world point:

  1. While the gunner gives aim input, do nothing and keep re-taking the point under the crosshair.
  2. When input stops, the last point stays locked.
  3. Every frame without input, compute the angles to that point from the current turret base and set them.
Example code – free to use
bool PlayerIsAiming(IEntity localChar)
{
	ChimeraCharacter ch = ChimeraCharacter.Cast(localChar);
	if (!ch || !ch.GetCharacterController())
		return false;
	CharacterInputContext ctx = ch.GetCharacterController().GetInputContext();
	return ctx && ctx.GetAimChange().LengthSq() > 1e-10;
}

void HoldPoint(TurretComponent turret, TurretControllerComponent ctrl, vector baseMat[4], bool aiming)
{
	vector dir = turret.GetAimingDirectionWorld().Normalized();
	vector eye = EyePosition(dir);

	if (aiming || !m_bHasPoint)
	{
		m_vPoint = eye + dir * TraceDistance(eye, dir);	// re-take the point
		m_bHasPoint = true;
		return;
	}

	vector local = MyMath.DirToLocal(baseMat, m_vPoint - eye).Normalized();
	vector m[4];
	Math3D.DirectionAndUpMatrix(local, "0 1 0", m);
	vector want = Math3D.MatrixToAngles(m);			// degrees: yaw, pitch

	vector cur = turret.GetAimingRotation();		// degrees
	float dYaw = MyMath.WrapDeg(want[0] - cur[0]);
	float dPitch = want[1] - cur[1];
	if (Math.AbsFloat(dYaw) + Math.AbsFloat(dPitch) < 0.02)
		return;

	ctrl.SetAimingAngles((cur[0] + dYaw) * Math.DEG2RAD, (cur[1] + dPitch) * Math.DEG2RAD);	// RADIANS
}
PitfallSetAimingAngles takes radians, GetAimingRotation returns degrees. Passing degrees drives both axes into their limits and freezes them there.
PitfallDo not measure small rotations with acos(dot): near 1 it produces ~0.02° of noise per frame, which looks like constant motion. Use the length of the cross product (= sin of the angle).
NoteLocking a point instead of a direction also compensates the walker's own travel: at 8 m/s a target at 300 m drifts 1.5°/s out of a direction-held sight.
PitfallFor a turret whose direction is the walking heading (the driver), do not lock the point under the crosshair. The driver usually looks slightly down, so that point lies on the ground a few metres ahead – walking towards and past it turns the walker by itself. Lock a point far along the sight instead (5 km, no trace).
PitfallDo not fall back to re-applying the old direction every frame either: it overwrites the player's mouse input and the walker cannot be turned at all. Keep the input priority of the point lock.
PitfallRe-take the point when it gets close (under about 25 m) or when a single frame would need more than 3° of correction. Walking past a locked point on the ground next to the walker otherwise swings the turret around at hundreds of degrees per second.
PitfallDetect teleports. If the turret base jumps by more than about 10° or 5 m in a single frame (Game Master moved the walker), treat it like aim input and re-take the point – otherwise the walker turns back to its old heading.
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