136 lines
8.0 KiB
C#
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}"); }
|
|
}
|
|
}
|