diff --git a/scripts/freecam.lua b/scripts/freecam.lua index 5bb5cd4..15c8ba4 100644 --- a/scripts/freecam.lua +++ b/scripts/freecam.lua @@ -74,10 +74,10 @@ local function current_facecam_target() local field = typeof(CS.RPG.Client.GamePhaseManager):GetField('_CurrentPhase', flags) local phase = manager and field and field:GetValue(manager) if not phase then return nil, nil, nil, false end - local entity + local entity, mode if typeof(CS.RPG.Client.BattleGamePhase):IsInstanceOfType(phase) then local instance = phase.BattleInstanceRef - local mode = instance and instance.TurnBasedGameModeRef + mode = instance and instance.TurnBasedGameModeRef entity = mode and mode.CurrentTurnOwnerEntity -- Inserted attacks can act outside the turn owner's turn. Prefer the -- most recently attacking entity while the Client reports one. @@ -91,15 +91,35 @@ local function current_facecam_target() return nil, nil, nil, false end if not entity then return nil, nil, nil, true end - if _G.__FC_FACE_ENTITY == entity.RuntimeID and _G.__FC_FACE_HEAD and _G.__FC_FACE_ROOT then + local function model_for(candidate) + local ok, result = pcall(function() + local component = candidate and candidate:GetComponent(typeof(CS.RPG.GameCore.CharacterModelComponent)) + return component and component:GetModelGO() + end) + if ok then return result end + return nil + end + local model = model_for(entity) + -- In a transformed turn the Client can report a model-less turn owner; + -- the live battle's MVP entity points to the visible transformed actor. + if not model and mode then + local fallback = mode.CurrentMVPEntity + local fallbackModel = model_for(fallback) + if fallbackModel then entity, model = fallback, fallbackModel end + end + if not model then return nil, nil, nil, true end + local cachedAlive, cachedActive = pcall(function() + return _G.__FC_FACE_HEAD and _G.__FC_FACE_ROOT + and _G.__FC_FACE_HEAD.gameObject.activeInHierarchy and _G.__FC_FACE_ROOT.gameObject.activeInHierarchy + end) + if _G.__FC_FACE_ENTITY == entity.RuntimeID and cachedAlive and cachedActive then return _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT, entity.RuntimeID, true end - local component = entity:GetComponent(typeof(CS.RPG.GameCore.CharacterModelComponent)) - local model = component and component:GetModelGO() - if not model then return nil, nil, nil, true end local nodes = model:GetComponentsInChildren(typeof(CS.UnityEngine.Transform), true) for i = 0, nodes.Length - 1 do - if nodes[i].name == 'Head_M' then return nodes[i], model.transform, entity.RuntimeID, true end + if nodes[i].name == 'Head_M' and nodes[i].gameObject.activeInHierarchy then + return nodes[i], model.transform, entity.RuntimeID, true + end end return nil, nil, nil, true end @@ -137,7 +157,14 @@ local function update_facecam(cam, settings) end end local head, root = _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT - if not head or not root or not head.gameObject.activeInHierarchy then return false end + local alive, active = pcall(function() return head and root and head.gameObject.activeInHierarchy end) + if not alive or not active then + -- Character transformations can briefly unload a model while keeping + -- the same entity ID. Give the replacement time to become available. + _G.__FC_FACE_MISSING_SINCE = _G.__FC_FACE_MISSING_SINCE or now + return now - _G.__FC_FACE_MISSING_SINCE < 2 + end + _G.__FC_FACE_MISSING_SINCE = nil local distance = _G.__FC_FACE_DISTANCE or tonumber(settings.distance) or 2 local scroll = Input.mouseScrollDelta.y if scroll ~= 0 then distance = distance - scroll * 0.2 end @@ -331,6 +358,7 @@ function _G.__FREECAM_TICK() _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = nil, nil _G.__FC_FACE_ENTITY = nil _G.__FC_FACE_DISTANCE, _G.__FC_FACE_LOCAL_FORWARD = nil, nil + _G.__FC_FACE_MISSING_SINCE = nil if _G.__FC_FACE_ANCHOR then CS.UnityEngine.Object.Destroy(_G.__FC_FACE_ANCHOR); _G.__FC_FACE_ANCHOR = nil end local label = settings.mode == 'facecam' and 'FaceCam' or 'FreeCam' show_ui('[' .. label .. ': OFF]') @@ -362,6 +390,7 @@ function _G.__FREECAM_TICK() _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = nil, nil _G.__FC_FACE_ENTITY = nil _G.__FC_FACE_DISTANCE, _G.__FC_FACE_LOCAL_FORWARD = nil, nil + _G.__FC_FACE_MISSING_SINCE = nil if _G.__FC_FACE_ANCHOR then CS.UnityEngine.Object.Destroy(_G.__FC_FACE_ANCHOR); _G.__FC_FACE_ANCHOR = nil end show_ui("FaceCam: character unavailable") end