#ifdef WORKBENCH
// The local native runner has one real player. A physical stand-in tests camera
// tracking; canonical multiplayer target selection is tested separately.
modded class SCR_PlayerController
{
 protected IEntity m_ERRCameraProbeTarget;
 void ERR_ProbeCameraTarget(IEntity target) { m_ERRCameraProbeTarget = target; }
 override IEntity ERR_ObservedEntity()
 {
  string outcome;
  if (m_ERRCameraProbeTarget && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "unconscious-camera") return m_ERRCameraProbeTarget;
  return super.ERR_ObservedEntity();
 }
 ERR_FollowSurvivorCameraComponent ERR_ProbeFollowComponent()
 {
  if (!m_ERRObserverCamera) return null;
  return ERR_FollowSurvivorCameraComponent.Cast(m_ERRObserverCamera.FindCameraComponent(ERR_FollowSurvivorCameraComponent));
 }
 bool ERR_ProbeCameraActive()
 {
  return m_ERRObserverCamera && GetGame().GetCameraManager().CurrentCamera() == m_ERRObserverCamera;
 }
}
modded class ERR_GameMode
{
 protected int m_ERRCameraStage = -2;
 protected int m_ERRCameraDeadline;
 protected IEntity m_ERRCameraWeapon;
 protected float m_ERRCameraWait;
 protected IEntity m_ERRCameraBody, m_ERRCameraTarget;
 protected SCR_CharacterDamageManagerComponent m_ERRCameraDamage;
 protected SCR_ManualCamera m_ERRCameraOther;
 protected int m_ERRViewSamples;
 protected override void EOnInit(IEntity owner)
 {
  super.EOnInit(owner);
  string outcome;
  if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "unconscious-camera") return;
  m_ERRCameraDeadline = System.GetTickCount() + 180000;
  GetGame().GetCallqueue().CallLater(ERR_CameraProbeTick, 500, true);
 }
 protected bool ERR_CameraCheck(bool ok, string message)
 {
  if (ok) { Print("ERR SMOKE PASS: unconscious camera " + message); return true; }
  Print("ERR SMOKE FAIL: unconscious camera " + message, LogLevel.ERROR);
  GetGame().GetCallqueue().Remove(ERR_CameraProbeTick); return false;
 }
 protected void ERR_CameraKnockout()
 {
  m_ERRCameraDamage.ForceUnconsciousness();
  array<ref SCR_PersistentDamageEffect> effects = m_ERRCameraDamage.GetAllPersistentEffectsOfType(SCR_PassiveHitZoneRegenDamageEffect);
  foreach (SCR_PersistentDamageEffect effect : effects) m_ERRCameraDamage.TerminateDamageEffect(effect);
  m_ERRCameraDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, true);
 }
 protected void ERR_CameraProbeTick()
 {
  if (System.GetTickCount() >= m_ERRCameraDeadline || !PreparationError().IsEmpty()) { ERR_CameraCheck(false, "deadline/preparation " + PreparationError()); return; }
  if (!Active()) return;
  SCR_PlayerController pc = SCR_PlayerController.Cast(GetGame().GetPlayerController());
  if (!pc || !pc.GetControlledEntity()) return;
  float now = GetWorld().GetWorldTime();
  if (now < m_ERRCameraWait) return;
  if (m_ERRCameraStage == -2)
  {
   if (!ERR_CameraCheck(Settings().Get("keepWeaponOnUnconscious") == 1, "weapon retention defaults to enabled")) return;
   m_ERRCameraBody = pc.GetControlledEntity();
   InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_ERRCameraBody.FindComponent(InventoryStorageManagerComponent));
   m_ERRCameraWeapon = ERR_Actors.Spawn("{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et", m_ERRCameraBody.GetOrigin()); Own(m_ERRCameraWeapon);
   if (!ERR_CameraCheck(m_ERRCameraWeapon && inventory && inventory.TryInsertItem(m_ERRCameraWeapon, EStoragePurpose.PURPOSE_WEAPON_PROXY), "one physical rifle granted to the player")) return;
   m_ERRCameraStage = -1; m_ERRCameraWait = now + 1500; return;
  }
  SCR_CharacterControllerComponent character = SCR_CharacterControllerComponent.Cast(m_ERRCameraBody.FindComponent(SCR_CharacterControllerComponent));
  InventoryStorageManagerComponent playerInventory = character.GetInventoryStorageManager();
  if (m_ERRCameraStage == -1)
  {
   BaseWeaponComponent weapon = BaseWeaponComponent.Cast(m_ERRCameraWeapon.FindComponent(BaseWeaponComponent));
   if (!ERR_CameraCheck(playerInventory.Contains(m_ERRCameraWeapon) && character.SelectWeapon(weapon), "rifle selected with native equip animation")) return;
   CameraHandlerComponent handler = CameraHandlerComponent.Cast(m_ERRCameraBody.FindComponent(CameraHandlerComponent));
   if (handler) handler.SetThirdPerson(false);
   m_ERRCameraStage = -10; m_ERRCameraWait = now + 2000; return;
  }
  if (m_ERRCameraStage == -10)
  {
   vector actual[4], observed[4], owner[4];
   pc.GetPlayerCamera().GetWorldTransform(actual);
   m_ERRCameraBody.GetWorldTransform(owner);
   ERR_FollowSurvivorCameraComponent.PlayerView(m_ERRCameraBody, observed);
   CharacterHeadAimingComponent head = character.GetHeadAimingComponent();
   PrintFormat("ERR VIEW COMPARE: sample=%1 native=%2 spectator=%3 body=%4 look=%5 head=%6 aim=%7", m_ERRViewSamples, actual[2], observed[2], owner[2], head.GetLookAngles(), head.GetAimingRotation(), ChimeraCharacter.Cast(m_ERRCameraBody).GetAimRotationModel());
   vector look[4], offset = "0 0.03 -0.07", rotation;
   head.GetLookTransformationMS(m_ERRCameraBody.GetAnimation().GetBoneIndex("Head"), EDirectBoneMode.RelativePosition, offset, rotation, look);
   PrintFormat("ERR VIEW POSITION: sample=%1 native=%2 spectator=%3 eyes=%4 lookMS=%5 lookDir=%6 lean=%7", m_ERRViewSamples, actual[3], observed[3], ChimeraCharacter.Cast(m_ERRCameraBody).EyePosition(), look[3].Multiply4(owner), look[2].Multiply3(owner), character.GetLeaning());
   if (++m_ERRViewSamples >= 26)
   {
    character.GetInputContext().SetFreelook(false);
    character.GetInputContext().SetLean(0);
    m_ERRCameraStage = 0;
    return;
   }
   if (m_ERRViewSamples < 10) character.GetInputContext().SetAimingAngles(Vector(m_ERRViewSamples * 0.12, 0.15, 0));
   else if (m_ERRViewSamples < 16)
   {
    character.GetInputContext().SetFreelook(true);
    character.GetInputContext().SetLookAtAngles(Vector(0.5, -0.2, 0));
   }
   else if (m_ERRViewSamples < 21)
   {
    character.GetInputContext().SetFreelook(false);
    character.GetInputContext().SetLean(1);
   }
   else character.GetInputContext().SetLean(-1);
   m_ERRCameraWait = now + 1000;
   return;
  }
  if (m_ERRCameraStage == 0)
  {
   if (!ERR_CameraCheck(character.GetWeaponManagerComponent().GetCurrentWeapon() && character.GetWeaponManagerComponent().GetCurrentWeapon().GetOwner() == m_ERRCameraWeapon, "original rifle actually in hand before knockout")) return;
   m_ERRCameraBody = pc.GetControlledEntity();
   m_ERRCameraDamage = SCR_CharacterDamageManagerComponent.Cast(m_ERRCameraBody.FindComponent(SCR_CharacterDamageManagerComponent));
   if (!ERR_CameraCheck(m_ERRCameraDamage && m_ERRCameraDamage.ERR_IsRevivablePlayer(), "admitted player retains treatable body")) return;
   ERR_CameraKnockout(); m_ERRCameraStage = 1; m_ERRCameraWait = now + 3000; return;
  }
  if (m_ERRCameraStage == 1)
  {
   if (!ERR_CameraCheck(pc.ERR_ObserverState() == ERR_ObserverReason.UNCONSCIOUS && pc.ERR_ProbeCameraActive(), "native knockout automatically opens the manual third-person camera")) return;
   if (!ERR_CameraCheck(pc.GetControlledEntity() == m_ERRCameraBody && ERR_Actors.Alive(m_ERRCameraBody) && !ERR_Actors.CanFight(m_ERRCameraBody), "possession and living unconscious body unchanged")) return;
   if (!ERR_CameraCheck(!pc.ERR_ObservedEntity() && vector.DistanceXZ(pc.ERR_ObserverAnchor(), m_ERRCameraBody.GetOrigin()) < 1, "no teammate falls back to the casualty body")) return;
   if (!ERR_CameraCheck(playerInventory.Contains(m_ERRCameraWeapon), "enabled retention keeps the exact rifle with the unconscious player")) return;
   vector point = ERR_Actors.Ground(m_ERRCameraBody.GetOrigin() + "25 0 25");
   m_ERRCameraTarget = ERR_Actors.Spawn(Factions().PrisonerPrefab, point); Own(m_ERRCameraTarget);
   if (!ERR_CameraCheck(m_ERRCameraTarget != null, "physical follow-target stand-in created")) return;
   pc.ERR_ProbeCameraTarget(m_ERRCameraTarget);
   m_ERRCameraStage = 2; m_ERRCameraWait = now + 1500; return;
  }
  if (m_ERRCameraStage == 2)
  {
   vector camera = GetGame().GetCameraManager().CurrentCamera().GetOrigin();
   PrintFormat("ERR CAMERA TRACK: camera=%1 target=%2 body=%3 active=%4 conscious=%5 reason=%6", camera, m_ERRCameraTarget.GetOrigin(), m_ERRCameraBody.GetOrigin(), pc.ERR_ProbeCameraActive(), ERR_Actors.CanFight(m_ERRCameraBody), pc.ERR_ObserverState());
   if (!ERR_CameraCheck(vector.Distance(camera, m_ERRCameraTarget.GetOrigin()) < 13 && vector.Distance(camera, m_ERRCameraBody.GetOrigin()) > 15, "camera follows target in 3D without moving the casualty")) return;
   m_ERRCameraTarget.SetOrigin(ERR_Actors.Ground(m_ERRCameraTarget.GetOrigin() + "15 0 0"));
   m_ERRCameraStage = 3; m_ERRCameraWait = now + 1500; return;
  }
  if (m_ERRCameraStage == 3)
  {
   if (!ERR_CameraCheck(vector.Distance(GetGame().GetCameraManager().CurrentCamera().GetOrigin(), m_ERRCameraTarget.GetOrigin()) < 13, "camera tracks a moving target")) return;
   InputManager input = GetGame().GetInputManager();
   ERR_FollowSurvivorCameraComponent follow = pc.ERR_ProbeFollowComponent();
   if (!ERR_CameraCheck(follow != null, "follow component exists")) return;
   ResourceName configuredInput;
   GameProject.GetModuleConfig("InputManagerSettings").Get("Default", configuredInput);
   PrintFormat("ERR CAMERA INPUT: default=%1 context=%2 previous=%3 next=%4 view=%5 orbit=%6", configuredInput, input.IsContextActive("ERR_ObserverContext"), input.IsActionActive("ERR_ObserverPrevious"), input.IsActionActive("ERR_ObserverNext"), input.IsActionActive("ERR_ObserverView"), input.IsActionActive("ManualCameraRotateYaw"));
   for (int inputIndex = 0; inputIndex < input.GetActionCount(); inputIndex++)
   {
    string inputName = input.GetActionName(inputIndex);
    if (inputName.Contains("Observer") || inputName == "ManualCameraRotateYaw") Print("ERR CAMERA INPUT REGISTERED: " + inputName);
   }
   if (!ERR_CameraCheck(follow.InputContextActive() && input.IsActionActive("ERR_ObserverPrevious") && input.IsActionActive("ERR_ObserverNext") && input.IsActionActive("ERR_ObserverView") && input.IsActionActive("ManualCameraRotateYaw"), "dedicated arrow/Enter actions and inherited orbit inputs loaded and active")) return;
   follow.Input(true, false, false, true);
   if (!ERR_CameraCheck(follow.IsPlayerView(), "Enter handler selects player view")) return;
   follow.Input(true, false, false, true);
   if (!ERR_CameraCheck(follow.IsPlayerView(), "held Enter does not toggle again")) return;
   follow.Input(true, false, false, false);
   m_ERRCameraStage = 30; m_ERRCameraWait = now + 1000; return;
  }
  if (m_ERRCameraStage == 30)
  {
   vector expected[4];
   if (!ERR_CameraCheck(ERR_FollowSurvivorCameraComponent.PlayerView(m_ERRCameraTarget, expected) && vector.Distance(GetGame().GetCameraManager().CurrentCamera().GetOrigin(), expected[3]) < 0.1, "native camera follows teammate head in player view")) return;
   pc.ERR_ProbeFollowComponent().Input(true, false, false, true);
   pc.ERR_ProbeFollowComponent().Input(true, false, false, false);
   m_ERRCameraStage = 31; m_ERRCameraWait = now + 1000; return;
  }
  if (m_ERRCameraStage == 31)
  {
   if (!ERR_CameraCheck(!pc.ERR_ProbeFollowComponent().IsPlayerView() && vector.Distance(GetGame().GetCameraManager().CurrentCamera().GetOrigin(), m_ERRCameraTarget.GetOrigin()) > 2, "second Enter restores orbit view")) return;
   Resource otherResource = Resource.Load("{EB1210AAE1BB8F54}Prefabs/ERR/SpectatorCamera.et");
   m_ERRCameraOther = SCR_ManualCamera.Cast(GetGame().SpawnEntityPrefab(otherResource, GetWorld()));
   if (!ERR_CameraCheck(m_ERRCameraOther && GetGame().GetCameraManager().CurrentCamera() == m_ERRCameraOther && !pc.ERR_ProbeCameraActive(), "separate manual camera takes over before recovery")) return;
   pc.ERR_ProbeCameraTarget(null); ERR_Actors.DeleteSpawned(m_ERRCameraTarget); m_ERRCameraTarget = null;
   m_ERRCameraDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, false);
   m_ERRCameraDamage.FullHeal(); m_ERRCameraStage = 4; m_ERRCameraWait = now + 4000; return;
  }
  if (m_ERRCameraStage == 4)
  {
   if (!ERR_CameraCheck(ERR_Actors.CanFight(m_ERRCameraBody) && pc.GetControlledEntity() == m_ERRCameraBody && pc.ERR_ObserverState() == ERR_ObserverReason.NONE && !pc.ERR_HasObserverCamera(), "native healing closes spectator and retains the original conscious player")) return;
   if (!ERR_CameraCheck(GetGame().GetCameraManager().CurrentCamera() == pc.GetPlayerCamera(), "normal player camera restored")) return;
   if (m_ERRCameraOther) { m_ERRCameraOther.Terminate(); delete m_ERRCameraOther; m_ERRCameraOther = null; }
   if (!ERR_CameraCheck(character.GetWeaponManagerComponent().GetCurrentWeapon() && character.GetWeaponManagerComponent().GetCurrentWeapon().GetOwner() == m_ERRCameraWeapon, "revival equips the same rifle without ground pickup")) return;
   if (!ERR_CameraCheck(Settings().Set("keepWeaponOnUnconscious", 0), "retention disabled through persistent setting")) return;
   ERR_CameraKnockout(); m_ERRCameraStage = 5; m_ERRCameraWait = now + 3000; return;
  }
  if (!ERR_CameraCheck(pc.ERR_ProbeCameraActive(), "second knockout reopens camera without stale target")) return;
  if (!ERR_CameraCheck(!playerInventory.Contains(m_ERRCameraWeapon), "disabled retention leaves normal native weapon drop on ground")) return;
  m_ERRCameraDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, false);
  m_ERRCameraDamage.FullHeal();
  GetGame().GetCallqueue().Remove(ERR_CameraProbeTick);
  Print("ERR SMOKE COMPLETE: unconscious spectator camera, physical tracking, retained treatment body, recovery and repeated knockout; target stand-in, not multiplayer proof");
 }
}
#endif
