     audio.listenerRotation = dir;
 }
 
+void UpdateFreelookCamera()
+{
+    float timeStep = time.timeStep;
+    float speedMultiplier = 1.0;
+    if (input.keyDown[KEY_LSHIFT])
+        speedMultiplier = 5.0;
+    if (input.keyDown[KEY_LCTRL])
+        speedMultiplier = 0.1;
+
+    if (input.keyDown['W'])
+        gameCameraNode.TranslateRelative(Vector3(0, 0, 1000) * timeStep * speedMultiplier);
+    if (input.keyDown['S'])
+        gameCameraNode.TranslateRelative(Vector3(0, 0, -1000) * timeStep * speedMultiplier);
+    if (input.keyDown['A'])
+        gameCameraNode.TranslateRelative(Vector3(-1000, 0, 0) * timeStep * speedMultiplier);
+    if (input.keyDown['D'])
+        gameCameraNode.TranslateRelative(Vector3(1000, 0, 0) * timeStep * speedMultiplier);
+
+    playerControls.yaw += mouseSensitivity * input.mouseMoveX;
+    playerControls.pitch += mouseSensitivity * input.mouseMoveY;
+    playerControls.pitch = Clamp(playerControls.pitch, -90, 90);
+    gameCameraNode.rotation = Quaternion(playerControls.pitch, playerControls.yaw, 0);
+}
+
 void UpdateStatus()
 {
-    if (engine.headless)
+    if (engine.headless || runServer)
         return;
 
     if (singlePlayer)


void HandlePostUpdate()
{
    // Not necessary on the server
    if (runServer)
        return;

    UpdateCamera();
    UpdateStatus();
}


void UpdateCamera()
{
    if (engine.headless)
        return;

    // On the server, use a simple freelook camera
    if (runServer)
    {
        UpdateFreelookCamera();
        return;
    }


    Node@ playerNode = FindOwnNode();
    if (playerNode is null)
        return;
