Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.PoliceBoardingFacing.cs
T

136 lines
8.0 KiB
C#

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}"); }
}
}