#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 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