Systems
View steering & shared turret seats
The driver steers with the mouse like on foot, the gunner and commander ride on a torso that turns and crouches – and nobody stands up from their seat while it moves.
The driver sits in a turret
A vanilla pilot seat routes input through the car simulation. The walker driver instead sits in a weaponless turret on the root. Its aim direction is the walking heading: look somewhere and the walker turns there. Forward/back and side steps come from a dedicated input context that also swallows the turret turn keys.
//! Driver machine, per frame while seated in the driver turret.
protected void DriverTick(IEntity walker, TurretComponent turret, float dt)
{
InputManager im = GetGame().GetInputManager();
im.ActivateContext("MyWalker_DriverContext");
float fwd = im.GetActionValue("MyWalker_DriveMove");
float side = im.GetActionValue("MyWalker_DriveStrafe");
vector d = turret.GetAimingDirectionWorld();
float heading = Math.Atan2(d[0], d[2]) * Math.RAD2DEG;
m_fSendTimer -= dt;
bool changed = Math.AbsFloat(MyMath.WrapDeg(heading - m_fSentHeading)) > 0.5;
if (m_fSendTimer > 0 && !changed)
return;
m_fSendTimer = 0.066; // ~15 per second, unreliable
m_fSentHeading = heading;
SCR_PlayerController pc = SCR_PlayerController.Cast(GetGame().GetPlayerController());
pc.MyWalker_SendDriverInput(walker, heading, fwd, side, MyDriveSettings.Pack());
}
The server checks that the sender really sits in this walker's driver turret, writes heading and inputs into RplProps and the physics owner drives with them (Leg drive). The local driver uses its own copy to avoid a round-trip delay.
Per-player settings
Dead zone and sensitivity are personal. They live in a ModuleGameSettings class (saved in the player
profile like the vanilla settings) and travel with the driver input, so every driver uses their own values on any
server.
class MyDriveSettings : ModuleGameSettings
{
[Attribute("5")] float m_fHeadingDeadZone; // deg
[Attribute("1")] float m_fSteerSensitivity; // x gain
[Attribute("0.15")] float m_fStickDeadZone; // 0..1
static float Get(string name, float fallback)
{
BaseContainer c = GetGame().GetGameUserSettings().GetModule("MyDriveSettings");
float v;
if (c && c.Get(name, v))
return v;
return fallback;
}
static void Set(string name, float v)
{
BaseContainer c = GetGame().GetGameUserSettings().GetModule("MyDriveSettings");
c.Set(name, v);
GetGame().UserSettingsChanged();
GetGame().SaveUserSettings();
}
}
The sliders appear under Settings → Controls in the mod's key binding category. The keybind sub-menu
(SCR_KeybindSetting) builds its rows from script, so a modded
ListActionsFromCurrentCategory() can append vanilla slider widgets after the bindings.
modded class SCR_KeybindSetting
{
override protected void ListActionsFromCurrentCategory()
{
super.ListActionsFromCurrentCategory();
SCR_KeyBindingCategory cat = m_KeybindConfig.m_KeyBindingCategories.Get(m_CategoriesSpinBox.GetCurrentIndex());
if (!cat || cat.m_sName != "MY_WALKER")
return;
Widget row = GetGame().GetWorkspace().CreateWidgets("{4A41296C0E9A889F}UI/layouts/WidgetLibrary/WLib_Slider.layout", m_wActionsLayout);
m_aRowWidgets.Insert(row); // cleared with the other rows on category change
SCR_SliderComponent s = SCR_SliderComponent.Cast(row.FindHandler(SCR_SliderComponent));
s.SetLabel("Steering dead zone (mouse)");
s.SetSliderSettings(0, 20, 0.5, "%1°");
s.SetValue(MyDriveSettings.Get("m_fHeadingDeadZone", 5));
s.GetOnChangedFinal().Insert(OnDeadZone);
}
protected void OnDeadZone(SCR_SliderComponent s, float v)
{
MyDriveSettings.Set("m_fHeadingDeadZone", v);
}
}
Crew turrets on a moving torso
Gunner, commander and driver turrets are slots on the root (they must be, for compartments and replication). The torso they sit in is a kinematic part that turns and crouches. Two tools keep them together:
EntitySlotInfo.SetAdditiveTransformLS()moves the whole turret with the torso (translation only).- A seat bone in the turret model gets the torso rotation. Moving a bone keeps the character seated; moving the passenger point every frame made characters stand up.
void FollowTorso(EntitySlotInfo slot, IEntity turretEnt, vector torsoDelta[4])
{
// 1) translate the turret with the torso
vector add[4];
Math3D.MatrixIdentity4(add);
add[3] = torsoDelta[3];
slot.SetAdditiveTransformLS(add);
// 2) rotate the seat via a bone, not via the passenger point
Animation anim = turretEnt.GetAnimation();
TNodeId bone = anim.GetBoneIndex("v_seat");
if (bone == -1) // TNodeId is an id, not an index: negative values are valid, only -1 is "none"
return;
vector m[4];
Math3D.MatrixCopy(torsoDelta, m);
m[3] = m_vSeatRest;
anim.SetBoneMatrix(turretEnt, bone, m);
}
CompartmentAccessComponent.IsGettingIn() / IsGettingOut()) and for a moment after – otherwise the get-in animation aims at a moving target.Who drives what
| Seat | Controls | Notes |
|---|---|---|
| Driver turret | walking heading, park, jump | stabilized in the world, never drives the torso |
| Gunner turret | torso yaw, arm pitch, arm weapons | the seat turns to face the aim direction |
| Commander turret | shoulder launchers, targeting, modules | launcher yaw relative to the torso, limited |
Seat rules keep AI and automation in line: the driver turret is never offered to AI, the vanilla pilot seat is only for AI drivers, and it is blocked while a player drives from the turret.