Synchronized Scene Weirdly Offset / General Troubles

I’m trying to recreate the stores from the base game and am currently struggling at getting the synchronized scenes for the robbing part to align. Besides that I currently replace the till before the scene, which feels like the wrong approach but the existing till is the wrong prop for “mp_am_holdup”?

The problem:

Here is the code snippet of relevance:

private async Task StartRobbery(Store store)
{
    store.isBeingRobbed = true;
    if (await ReplaceTill(new Vector3(store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z)))
    {
        store.hasReplacedTill = true;
    }
    Ped clerk = GetClerk();
    if (store == null || clerk == null) { throw new ArgumentNullException("Could not get store or coordinates!"); };

    Model bagModel = new Model("p_poly_bag_01_s");
    bagModel.Request();
    while (!bagModel.IsLoaded) { await Delay(0); }

    Entity bag = await World.CreateProp(bagModel, clerk.Position - new Vector3(0, 0, 1.0f), false, false);
    Entity till = new Prop(GetClosestObjectOfType(store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 1.0f, 892543765, false, false, false));

    RequestAnimDict("mp_am_hold_up");
    while (!HasAnimDictLoaded("mp_am_hold_up")) { await Delay(0); }

    Vector3 initAnimPos = GetAnimInitialOffsetPosition("mp_am_hold_up", "holdup_victim_20s", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 2);
    Vector3 initAnimHeading = GetAnimInitialOffsetRotation("mp_am_hold_up", "holdup_victim_20s", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 2);

    clerk.Task.ClearAllImmediately();
    int netScene = NetworkCreateSynchronisedScene(initAnimPos.X, initAnimPos.Y, initAnimPos.Z, initAnimHeading.X, initAnimHeading.Y, initAnimHeading.Z, 1, false, false, 1, 0, 1);
    NetworkAddPedToSynchronisedScene(clerk.Handle, netScene, "mp_am_hold_up", "holdup_victim_20s", 4, -8, 16, 1, 2, 0);
    NetworkAddEntityToSynchronisedScene(till.Handle, netScene, "mp_am_holdup", "holdup_victim_20s_till", 8, -8, 0);
    NetworkAddEntityToSynchronisedScene(bag.Handle, netScene, "mp_am_holdup", "holdup_victim_20s_bag", 8, -8, 1);
    NetworkStartSynchronisedScene(netScene);

    int localId = -1;
    while (localId == -1)
    {
        await Delay(0);
        localId = NetworkGetLocalSceneFromNetworkId(netScene);
    }

    while (GetSynchronizedScenePhase(localId) <= 0.9)
    {
        await Delay(0);
    }
    Pickup bagPickup = await World.CreatePickup(PickupType.MoneyPaperBag, bag.Position, bagModel, 1000);
    bag.Delete();
    NetworkStopSynchronisedScene(netScene);
    clerk.Task.ReactAndFlee(Game.PlayerPed);
    store.isBeingRobbed = false;
    store.hasBeenRobbed = true;
}

Here is the code in its entirety:

using System;
using System.Threading.Tasks;
using CitizenFX.Core;
using static CitizenFX.Core.Native.API;
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Stores.Client
{
    public class Stores : BaseScript
    {
        private List<Store> stores = new List<Store>();
        private List<int> interiorIds = new List<int>();
        private Dictionary<int, int> IdxPedHandles = new Dictionary<int, int>();
        private int storeNum = 1;

        public Stores()
        {
            Tick += OnTick;
            RegisterExports();
        }

        //----------------------------------- Store Loading / Initialization -------------------

        private async Task LoadStores()
        {
            try
            {
                string storeString = LoadResourceFile("stores", "stores.json");

                if (string.IsNullOrEmpty(storeString))
                {
                    throw new ArgumentException("stores.json not found or is empty");
                }

                var data = JsonConvert.DeserializeObject<StoreData>(storeString);

                if (data.Stores == null)
                {
                    throw new ArgumentException("No 'stores' in JSON data");
                }

                foreach (var store in data.Stores)
                {
                    var parsedStore = new Store
                    {
                        Location = new Vector3(store.Location.X, store.Location.Y, store.Location.Z),
                        AnimationOrigin = new Vector4(store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, store.AnimationOrigin.W),
                        PedOrigin = new Vector4(store.PedOrigin.X, store.PedOrigin.Y, store.PedOrigin.Z, store.PedOrigin.W),
                        defaultName = store.defaultName,
                        Name = store.defaultName ? "Store" : store.Name
                    };

                    AddStore(parsedStore);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Error loading stores: {ex.Message}");
            }
        }

        private void ValidateStore(Store store)
        {
            if (float.IsNaN(store.Location.X) || float.IsNaN(store.Location.Y) || float.IsNaN(store.Location.Z))
            {
                throw new ArgumentException("Invalid store location");
            }
            if (store.Name == null && !store.defaultName)
            {
                throw new ArgumentException("Store name is required if not default used");
            }
        }

        private int AddStore(Store store)
        {
            ValidateStore(store);

            store.Idx = storeNum++;
            stores.Add(store);
            return store.Idx;
        }

        public async void StartStores()
        {
            await LoadStores();
            CreateStoreBlips();
            CreateStoreClerks();

        }

        private class Store
        {
            public Vector3 Location { get; set; }
            public Vector4 AnimationOrigin { get; set; }
            public Vector4 PedOrigin { get; set; }
            public bool defaultName { get; set; }
            public string Name { get; set; }
            public bool isBeingRobbed { get; set; }
            public bool hasBeenRobbed { get; set; }
            public bool hasReplacedTill { get; set; }
            public int Idx { get; set; }
        }
        private class StoreData
        {
            public List<Store> Stores { get; set; }
        }

        //----------------------------------- Store Blips / Store Clerks --------------------------------------

        private void CreateStoreBlips()
        {
            foreach (Store store in stores)
            {
                Blip blip = World.CreateBlip(new Vector3(store.Location.X, store.Location.Y, store.Location.Z));
                blip.Sprite = BlipSprite.Store;
                blip.Scale = 1f;
                blip.IsShortRange = true;
                blip.Name = store.defaultName ? "Store" : store.Name;

                interiorIds.Add(GetInteriorAtCoords(store.Location.X, store.Location.Y, store.Location.Z));
            }
        }

        private async void CreateStoreClerks()
        {
            foreach (Store store in stores)
            {
                Ped storePed = await World.CreatePed(PedHash.ShopKeep01, GetOffsetFromCoordAndHeadingInWorldCoords(store.PedOrigin.X, store.PedOrigin.Y, store.PedOrigin.Z, store.PedOrigin.W, 0f, -0.1f, 0f), store.PedOrigin.W);
                storePed.BlockPermanentEvents = true;
                storePed.IsPersistent = true;
                IdxPedHandles.Add(store.Idx, storePed.Handle);
            }
        }

        [EventHandler("createStoreBlips")]

        //----------------------------------- Store Logic --------------------------------------2

        private async Task OnTick()
        {
            await IsStoreBeingRobbedThisFrame();
        }

        private Store GetStore()
        {
            foreach (var store in stores)
            {
                if (GetInteriorAtCoords(store.Location.X, store.Location.Y, store.Location.Z) == GetInteriorFromEntity(Game.PlayerPed.Handle))
                {
                    return store;
                }
            }
            return null;
        }

        private Ped GetClerk()
        {
            if (IsPlayerInStore())
            {
                Store store = GetStore();
                if (IdxPedHandles.TryGetValue(store.Idx, out int pedHandle))
                {
                    return new Ped(pedHandle);
                }
            }
            return null;
        }

        private bool IsPlayerArmed()
        {
            return !(new Player(Game.Player.Handle).Character.Weapons.Current.Hash == WeaponHash.Unarmed);
        }

        private bool IsPlayerInStore()
        {
            if (interiorIds.Contains(GetInteriorFromEntity(Game.PlayerPed.Handle)))
            {
                return true;
            }
            return false;
        }

        private bool IsClerkAimedAt()
        {
            Ped clerk = GetClerk();
            if (clerk != null)
            {
                return IsPlayerFreeAimingAtEntity(Game.Player.Handle, clerk.Handle);
            }
            return false;
        }

        private async Task<bool> ReplaceTill(Vector3 animOrigin)
        {
            int entity = GetClosestObjectOfType(animOrigin.X, animOrigin.Y, animOrigin.Z, 1.0f, 303280717, false, false, false);
            if (DoesEntityExist(entity))
            {
                Entity till;
                Model tillModel = new Model("p_till_01_s");

                till = new Prop(entity);
                Vector3 position = till.Position;
                Vector3 rotation = till.Rotation;
                till.Delete();

                tillModel.Request();
                while (!tillModel.IsLoaded)
                {
                    await Delay(0);
                }

                till = await World.CreateProp(tillModel, new Vector3(position.X, position.Y, position.Z - 0.12f), false, false); // -0.12f is a hardcoded value to make sure the till is placed correctly
                till.Rotation = rotation;
                return true;
            }
            return false;
        }

        private async Task IsStoreBeingRobbedThisFrame()
        {
            if (IsClerkAimedAt() && IsPlayerArmed())
            {
                Store store = GetStore();
                if (!store.isBeingRobbed && !store.hasBeenRobbed)
                {
                    store.isBeingRobbed = true;
                    await StartRobbery(store);
                }
            }
        }

        private async Task StartRobbery(Store store)
        {
            store.isBeingRobbed = true;
            if (await ReplaceTill(new Vector3(store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z)))
            {
                store.hasReplacedTill = true;
            }
            Ped clerk = GetClerk();
            if (store == null || clerk == null) { throw new ArgumentNullException("Could not get store or coordinates!"); };

            Model bagModel = new Model("p_poly_bag_01_s");
            bagModel.Request();
            while (!bagModel.IsLoaded) { await Delay(0); }

            Entity bag = await World.CreateProp(bagModel, clerk.Position - new Vector3(0, 0, 1.0f), false, false);
            Entity till = new Prop(GetClosestObjectOfType(store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 1.0f, 892543765, false, false, false));

            RequestAnimDict("mp_am_hold_up");
            while (!HasAnimDictLoaded("mp_am_hold_up")) { await Delay(0); }

            Vector3 initAnimPos = GetAnimInitialOffsetPosition("mp_am_hold_up", "holdup_victim_20s", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 2);
            Vector3 initAnimHeading = GetAnimInitialOffsetRotation("mp_am_hold_up", "holdup_victim_20s", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 2);

            clerk.Task.ClearAllImmediately();
            int netScene = NetworkCreateSynchronisedScene(initAnimPos.X, initAnimPos.Y, initAnimPos.Z, initAnimHeading.X, initAnimHeading.Y, initAnimHeading.Z, 1, false, false, 1, 0, 1);
            NetworkAddPedToSynchronisedScene(clerk.Handle, netScene, "mp_am_hold_up", "holdup_victim_20s", 4, -8, 16, 1, 2, 0);
            NetworkAddEntityToSynchronisedScene(till.Handle, netScene, "mp_am_holdup", "holdup_victim_20s_till", 8, -8, 0);
            NetworkAddEntityToSynchronisedScene(bag.Handle, netScene, "mp_am_holdup", "holdup_victim_20s_bag", 8, -8, 1);
            NetworkStartSynchronisedScene(netScene);

            int localId = -1;
            while (localId == -1)
            {
                await Delay(0);
                localId = NetworkGetLocalSceneFromNetworkId(netScene);
            }

            while (GetSynchronizedScenePhase(localId) <= 0.9)
            {
                await Delay(0);
            }
            Pickup bagPickup = await World.CreatePickup(PickupType.MoneyPaperBag, bag.Position, bagModel, 1000);
            bag.Delete();
            NetworkStopSynchronisedScene(netScene);
            clerk.Task.ReactAndFlee(Game.PlayerPed);
            store.isBeingRobbed = false;
            store.hasBeenRobbed = true;
        }

        //----------------------------------- Exports ------------------------------------------

        private void RegisterExports()
        {
            Exports.Add("createStores", new Action(StartStores));
        }
    }
}

If needed here’s also the .json:

{
    "stores": [
        {
            "Location": {
                "X": -711.6182,
                "Y": -915.7814,
                "Z": 19.2156
            },
            "AnimationOrigin": {
                "X": -706.6382,
                "Y": -913.6887,
                "Z": 19.32968,
                "W": 90.00
            },
            "defaultName": true,
            "Name": "1"
        },
        {
            "Location": {
                "X": -52.2386,
                "Y": -1755.9198,
                "Z": 29.421
            },
            "AnimationOrigin": {
                "X": -47.19872,
                "Y": -1757.67,
                "Z": 29.53509,
                "W": 50.00
            },
            "defaultName": true,
            "Name": "2"
        },
        {
            "Location": {
                "X": 1159.5792,
                "Y": -325.6018,
                "Z": 69.2052
            },
            "AnimationOrigin": {
                "X": 1164.206,
                "Y": -322.8901,
                "Z": 69.31918,
                "W": 100.00
            },
            "defaultName": true,
            "Name": "3"
        },
        {
            "Location": {
                "X": 1699.6136,
                "Y": 4928.5015,
                "Z": 42.0637
            },
            "AnimationOrigin": {
                "X": 1698.307,
                "Y": 4923.371,
                "Z": 42.17774,
                "W": 325.00
            },
            "defaultName": true,
            "Name": "4"
        },
        {
            "Location": {
                "X": -1822.7968,
                "Y": 788.9506,
                "Z": 138.1877
            },
            "AnimationOrigin": {
                "X": -1820.465,
                "Y": 793.8166,
                "Z": 138.2128,
                "W": 132.50
            },
            "defaultName": true,
            "Name": "5"
        },
        {
            "Location": {
                "X": 1166.4976,
                "Y": 2704.6975,
                "Z": 38.1695
            },
            "AnimationOrigin": {
                "X": 1165.958,
                "Y": 2710.201,
                "Z": 38.26217,
                "W": 178.85
            },
            "defaultName": true,
            "Name": "6"
        },
        {
            "Location": {
                "X": -2972.6047,
                "Y": 390.827,
                "Z": 15.0551
            },
            "AnimationOrigin": {
                "X": -2967.027,
                "Y": 390.9038,
                "Z": 15.14779,
                "W": 85.25
            },
            "defaultName": true,
            "Name": "7"
        },
        {
            "Location": {
                "X": -1226.0824,
                "Y": -903.3079,
                "Z": 12.3382
            },
            "AnimationOrigin": {
                "X": -1222.331,
                "Y": -907.8233,
                "Z": 12.43084,
                "W": 32.70
            },
            "defaultName": true,
            "Name": "8"
        },
        {
            "Location": {
                "X": 1140.3356,
                "Y": -980.9802,
                "Z": 46.4276
            },
            "AnimationOrigin": {
                "X": 1134.812,
                "Y": -982.3615,
                "Z": 46.52031,
                "W": 276.70
            },
            "defaultName": true,
            "Name": "9"
        },
        {
            "Location": {
                "X": -1490.2791,
                "Y": -382.8716,
                "Z": 40.1752
            },
            "AnimationOrigin": {
                "X": -1486.673,
                "Y": -378.4638,
                "Z": 40.26789,
                "W": 133.75
            },
            "defaultName": true,
            "Name": "10"
        },
        {
            "Location": {
                "X": -3240.8726,
                "Y": 1004.6915,
                "Z": 12.8307
            },
            "AnimationOrigin": {
                "X": -3244.573,
                "Y": 1000.658,
                "Z": 12.94538,
                "W": 355.00
            },
            "defaultName": true,
            "Name": "11"
        },
        {
            "Location": {
                "X": -3039.5024,
                "Y": 589.4763,
                "Z": 7.9089
            },
            "AnimationOrigin": {
                "X": -3041.357,
                "Y": 584.2665,
                "Z": 8.023597,
                "W": 17.75
            },
            "defaultName": true,
            "Name": "12"
        },
        {
            "Location": {
                "X": 544.2382,
                "Y": 2671.7927,
                "Z": 42.1565
            },
            "AnimationOrigin": {
                "X": 548.9015,
                "Y": 2668.941,
                "Z": 42.27118,
                "W": 97.50
            },
            "defaultName": true,
            "Name": "13"
        },
        {
            "Location": {
                "X": 2558.3494,
                "Y": 385.5382,
                "Z": 108.6229
            },
            "AnimationOrigin": {
                "X": 2554.875,
                "Y": 381.3858,
                "Z": 108.7376,
                "W": 357.75
            },
            "defaultName": true,
            "Name": "14"
        },
        {
            "Location": {
                "X": 2681.396,
                "Y": 3283.1792,
                "Z": 55.2411
            },
            "AnimationOrigin": {
                "X": 2676.212,
                "Y": 3280.969,
                "Z": 55.35582,
                "W": 330.85
            },
            "defaultName": true,
            "Name": "15"
        },
        {
            "Location": {
                "X": 1731.7385,
                "Y": 6412.0957,
                "Z": 35.0372
            },
            "AnimationOrigin": {
                "X": 1729.329,
                "Y": 6417.123,
                "Z": 35.15191,
                "W": 243.65
            },
            "defaultName": true,
            "Name": "16"
        },
        {
            "Location": {
                "X": 1964.8656,
                "Y": 3741.5476,
                "Z": 32.3437
            },
            "AnimationOrigin": {
                "X": 1959.323,
                "Y": 3742.289,
                "Z": 32.45843,
                "W": 300.00
            },
            "defaultName": true,
            "Name": "17"
        },
        {
            "Location": {
                "X": 29.3065,
                "Y": -1348.3275,
                "Z": 29.497
            },
            "AnimationOrigin": {
                "X": 24.94562,
                "Y": -1344.954,
                "Z": 29.6117,
                "W": 270.00
            },
            "defaultName": true,
            "Name": "18"
        },
        {
            "Location": {
                "X": 376.8703,
                "Y": 324.1652,
                "Z": 103.5665
            },
            "AnimationOrigin": {
                "X": 373.5954,
                "Y": 328.5892,
                "Z": 103.6811,
                "W": 255.85
            },
            "defaultName": true,
            "Name": "19"
        },
        {
            "Location": {
                "X": 1393.825,
                "Y": 3600.7297,
                "Z": 34.981
            },
            "AnimationOrigin": {
                "X": 1393.072,
                "Y": 3605.959,
                "Z": 35.11384,
                "W": 200.00
            },
            "defaultName": true,
            "Name": "20"
        }
    ]
}

I’d really appreciate help of any kind. Even if you are only familiar with the lua equivalents please do respond!!!

Seems like whatever the AnimationOrigin is gets misplaced, pretty weird. Is the offset the same across multiple stores or it only breaks in one/several, but some other store works fine?

1 Like

Always the same offset! Across stores that face in complete different directions relative to the world coordinate grid, so that can’t be it. I agree with your “conclusion” but id have no idea what would be causing that!

So I just added:

            Debug.WriteLine($"store.AnimationOrigin: {store.AnimationOrigin}");
            Debug.WriteLine($"initAnimPos: {initAnimPos}");

Let me see if what I stated is actually correct!

I agree with @_4iY seems like the objects origin is off. If it is then at least you can compensate.

Only issue if it is is that its bounds may make it so you can interact with the register from weird places.

The thing is replacing the tills without the synchronized scene works perfectly for my AnimationOrigin coords. No offset at all! But the moment the synchronized scene starts everything just snaps up and over. But the till is the origin of the synchronized scene so why is this happening?

Ok I’ve never used this before, so its just an educated guess.

You add the till to the scene.
You then set an animation offset
you then create a syncronisedscene using that offset
you then add your till to that scene

my guess is when you add the till its using the offset values of the synchronised scene as its origin. So maybe adjust the position of the till with the same amount of negative offset after you have added it to the scene?

might be way off but its all i can see from what you have there

Ok so I did some research and the coords fed into my scene are indeed the ones from my json:

store.AnimationOrigin: X:-706.6382 Y:-913.6887 Z:19.32968 W:90
Debug Output: X:-706.6382 Y:-913.6887 Z:19.32968

Is my basic approach wrong? I used Codewalker to get these coords and they work for replacing the till?

So confusing!

Happy to try anything at this point, so no worries!

In regards to:

You add the till to the scene.
You then set an animation offset

What exactly do you mean by this?
Another NetworkSynchronisedScene?
I know CreateSynchronisedScene() exists but have never used it nor know anything about it tbh.

Ok new development!

Using:

Vector3 initAnimPos = GetAnimInitialOffsetPosition("mp_am_hold_up", "holdup_victim_20s_till", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 0);
Vector3 initAnimHeading = GetAnimInitialOffsetRotation("mp_am_hold_up", "holdup_victim_20s_till", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 0);

instead of:

Vector3 initAnimPos = GetAnimInitialOffsetPosition("mp_am_hold_up", "holdup_victim_20s", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 0);
Vector3 initAnimHeading = GetAnimInitialOffsetRotation("mp_am_hold_up", "holdup_victim_20s", store.AnimationOrigin.X, store.AnimationOrigin.Y, store.AnimationOrigin.Z, 0, 0, store.AnimationOrigin.W, 0, 0);

Marginally improved the situation but its still nowhere close.

Before:

After:

Ok so that list i went through are the steps in YOUR code. I was just reiterating them.

You get some offsets here:

This is when you create your scene.

int netScene = NetworkCreateSynchronisedScene(initAnimPos.X, initAnimPos.Y, initAnimPos.Z, initAnimHeading.X, initAnimHeading.Y, initAnimHeading.Z, 1, false, false, 1, 0, 1);

After that you are adding the till and the ped
, are they recieving these offsets some how? If so once the til is added to the scene can you move it?

1 Like

AFAIK (which is not much) the way NetworkSynchronisedScenes work is they consist of entities and peds.

In my case:

<SynchedAnim title="Store hold up cash" category="Individual w/props" actorsAligned="true" deltaZ="0">
<ActorAnim animIndex="13107" ("holdup_victim_20s")/><ObjectAnim animIndex="13108" ("holdup_victim_20s_till") /><ObjectAnim animIndex="13109" (holdup_victim_20s_bag") /><Object propName="p_poly_bag_01_s"/><Object propName="p_till_01_s"/>
</SynchedAnim>

And these entities and peds have a preset position within a NetworkSynchronisedScene, with one entity or ped being the “origin” for the entire scene, which is usually a central entity of the scene.
In my case the “till” aka cash register (assumption). I’d also assume the scene gives them their new positions, which can be seen when the scene plays and all entities and peds snap.

But even if the initial location of the prop till propagates into the scene shouldn’t that still work since my prop till is at the correct spot before the scene?

I don’t at all understand

GetAnimInitialOffsetPosition(...);
GetAnimInitialOffsetRotation(...);

at all tbh. They were mentioned to be important and do seem to improve the situation.

In order to make proper use of synchronized scenes the animations MUST be setup for this. “Being setup for this” refers to that they have properly set Initial Anim Offset Position/Rotations (Which can be obtained in scripts with GetAnimInitialOffsetPosition & GetAnimInitialOffsetRotation )
If your (or, realistically rockstars) animations are NOT setup for this, you will most likely have all objects piled up on each other and not behaving appropriately.

Since both don’t return values that seem like offsets to add/subtract/multiply with world coordinates but more like actual adjusted coordinates this is how I used them.

The guide warns that networked scenes work unreliable with more than 2 players but that “Local synchronized scenes work fine” so I would hope that’s not the issue.

I know I could probably just subtract / add an offset using GetOffsetFromCoordAndHeadingInWorldCoords but that seems like a really cheap approach.

I’m mostly going off this guide: Network Synchronized Scenes - Technical Documentation - rage.re

I agree just putting a bandaid on it. unfortunately, i dont know the actual root cause i was just trying to point out a possible fix hppe u het sorted.

1 Like

Yeah I just did a -0.25f and that seems to work for now :sob:

1 Like

what xml did you used to find the actor / object data