using Ananta.SDK.Network; using Ananta.SDK.Serialization; using Ananta.Server.Protocol.Client4229938; namespace Ananta.Server.Handlers.Game; internal sealed partial class GameRouter { // Temporary mesh yaw only. The original paired action owns translation; // completed seating, alighting and passenger bindings remain untouched. private static async Task PoliceBoardingFacing4229938(TcpSession session, PoliceRuntime4229938 runtime, bool restore) { ulong agent = runtime.Agent; var passenger = runtime.PendingBoarding; if (!restore) { if (passenger == null || passenger.Seat is not (2 or 3) || !runtime.Npcs.TryGetValue(agent, out var npc) || npc.Template != 4065053) return; var vehicles = GetVehicleRuntimeState(session); lock (vehicles.SyncRoot) if (!vehicles.Vehicles.TryGetValue(passenger.Vehicle, out var car) || car.ConfigId != 81004027) return; } if (!session.Items.TryGetValue(PoliceGateSessionKey4229938, out var gateValue) || gateValue is not TcpSession gate || gate.ClosedToken.IsCancellationRequested) return; ulong vehicleId = passenger?.Vehicle ?? 0; var script = $$""" local ok, err = pcall(function() _G.__lixiBoardFacing = _G.__lixiBoardFacing or {} local key = '{{agent}}' local saved = _G.__lixiBoardFacing[key] if {{(restore ? "true" : "false")}} then if saved then saved.stop() end return end if saved then return end if type(ReflectionGetProperty) ~= 'function' or type(ReflectionCallMethod) ~= 'function' then print_error('[LIXI-BOARD] facing-v2 skipped: reflection bridge unavailable'); return end local id = ulong.new({{(uint)agent}}, {{agent >> 32}}) local npc = gCS.SceneDataMgr.GetUnit(id) local car = LX6.Drive.DriveManager.Instance:GetBaseVehicle(ulong.new({{(uint)vehicleId}}, {{vehicleId >> 32}})) if not npc or not npc.MeshRoot or not car then return end local node = ReflectionGetProperty('LX6.Drive.BaseVehicle','vehicleNode',car) local seat = node and ReflectionCallMethod('LX6.Drive.VehicleNodeModule','GetSeatTransform',node,{{passenger?.Seat ?? 0}}) local seatData if not seat then -- This police SUV has no seat Transform (confirmed in log). -- Its native boarding binding uses configured seat offsets. local seats = ReflectionGetProperty('LX6.Drive.BaseVehicle','seatModule',car) if seats then seatData = ReflectionCallMethod('LX6.Drive.VehicleSeatModule','get_Item',seats,{{passenger?.Seat ?? 0}}) end if not seatData then print_error('[LIXI-BOARD] facing-v2 skipped: no seat data'); return end end local function seatPosition() if seat then return seat.position end return ReflectionCallMethod('LX6.Drive.VehicleSeatData','GetSeatWorldPosition',seatData,0) end local function seatYaw() if seat then return seat.eulerAngles.y end local e = ReflectionCallMethod('LX6.Drive.VehicleSeatData','GetSeatEulerOffset',seatData) return car.EulerY + e.y end local root = npc.MeshRoot local angles = root.localEulerAngles local rotation = root.localRotation local startY = npc.Position.Y - car.gameObject.transform.position.y local s = {root=root, rotation=rotation, progress=0, applied=false, started=UnityEngine.Time.realtimeSinceStartup, stopped=false} local hook = 'lixi-board-facing-' .. key s.stop = function() if s.stopped then return end s.stopped = true if s.hook == 'camera' then pcall(function() gCameraUtils.RemoveCameraUpdateEvent(hook) end) end if s.timer then pcall(function() s.timer:Stop() end) end if s.applied then pcall(function() root.localRotation = rotation end) end if _G.__lixiBoardFacing[key] == s then _G.__lixiBoardFacing[key] = nil end end s.tick = function() if s.stopped then return end local success, failure = pcall(function() if UnityEngine.Time.realtimeSinceStartup-s.started >= 15 or gCS.SceneDataMgr.GetUnit(id) ~= npc or npc.MeshRoot ~= root then s.stop(); return end local p, target = npc.Position, seatPosition() local dx, dz = p.X-target.x, p.Z-target.z local rise = p.Y-car.gameObject.transform.position.y-startY -- Never rotate the walk-to-door phase. Only blend the -- recorded sideways pose as the root rises into its seat. local delta = (seatYaw()-npc.WorldEulerY+180)%360-180 if dx*dx+dz*dz <= 0.4225 and rise > 0.10 and math.abs(delta) >= 45 and math.abs(delta) <= 135 then local u = math.max(0,math.min(1,(rise-0.10)/0.20)) s.progress = math.max(s.progress,u*u*(3-2*u)) if not s.applied then print_error('[LIXI-BOARD] facing-v2 blend agent=' .. key .. ' yawDelta=' .. tostring(delta)) end s.applied = true root.localRotation = UnityEngine.Quaternion.Euler(angles.x,angles.y+delta*s.progress,angles.z) elseif s.applied then -- If native root orientation catches up, do not keep -- an extra quarter turn layered over correct seating. root.localRotation = rotation s.applied = false end end) if not success then s.stop(); print_error('[LIXI-BOARD] facing-v2 stopped: ' .. tostring(failure)) end end _G.__lixiBoardFacing[key] = s local installed = false if gCameraUtils and gCameraUtils.AddCameraUpdateEvent and gCameraUtils.RemoveCameraUpdateEvent then s.hook = 'camera' installed = pcall(function() gCameraUtils.AddCameraUpdateEvent(hook,s.tick) end) if not installed then pcall(function() gCameraUtils.RemoveCameraUpdateEvent(hook) end) s.hook = nil end end if not installed and Timer and Timer.New then installed = pcall(function() s.timer=Timer.New(s.tick,0.02,-1,true);s.timer:Start() end) if installed then s.hook = 'timer' end end if not installed then s.stop(); error('no camera/timer hook') end s.tick() if not s.stopped then print_error('[LIXI-BOARD] facing-v2 installed agent=' .. key .. ' hook=' .. s.hook .. ' source=' .. (seat and 'transform' or 'seatData')) end end) if not ok then print_error('[LIXI-BOARD] facing-v2 failed: ' .. tostring(err)) end """; try { await gate.NotifyAsync(MethodId.SyncDoToLuaMaster, new UxWriter().UxString(script).ToArray(), session.ClosedToken); } catch (OperationCanceledException) when (session.ClosedToken.IsCancellationRequested) { throw; } catch (Exception ex) { session.Log.Warn($"[LIXI-BOARD] facing unavailable: {ex.GetType().Name}"); } } }