[C#] Black screen on 2nd connect to server

So when a player connects for the first time they have a camera that is set to a random position within the world and they can select a character which then changes the camera into a gameplay camera.

If the player was to disconnect and reconnect to the server, on reconnecting after the loading screen they get a black screen with a loading symbol in the bottom right as if it is trying to load something, the NUI menu is still visible and you can still choose a character, but the camera doesn’t come back. No errors on either client console or server console.

If the client restarts their FiveM and connects to the server they get the camera again. But if the server restarts and the client connects again without restarting their FiveM, they will have the black screen.

Any ideas what would be stopping the camera to load but only on the second connection? Could the client not have disconnected properly?

1 Like

Can’t help too much without seeing the code; go ahead and post it here.

Well I don’t know exactly where everything is going wrong, and it’s a whole framework project more than just a couple hundred lines. I can post what I think may be the issue or where it’s having issues which is around the character spawning and the camera being set.

This is the player spawning, I’ve taken out a lot of the lines regarding changing the 'Game.Player.Character’s style, face features, etc. This is on the client.

public async void HandlePlayerSpawn(string _character)
        {
            Character _characterData = JsonConvert.DeserializeObject<Character>(_character);
            Utils.WriteCharacter(_characterData);
            bool modelChange = await Game.Player.ChangeModel(new Model(_characterData.Model));
            while (!modelChange) { await Game.Player.ChangeModel(new Model(_characterData.Model)); }
            Utils.WriteLine($"Set character looks! {modelChange}");
            Vector3 SpawnPosition = new Vector3();
            float SpawnHeading;

            if (_characterData.isNew)
            {
                SpawnPosition.X = 0f;
                SpawnPosition.Y = 0f;
                SpawnPosition.Z = 75f;
                SpawnHeading = 179.8418f;

                Game.Player.Character.Position = SpawnPosition;
                Game.Player.Character.Heading = SpawnHeading;

                //Enable character modifing
                CharacterModifier.EnableCharacterModifier(_characterData);
            }
            else
            {
                //SpawningConfig newSpawn = ConfigManager.SpawningConfig[new Random().Next(0, ConfigManager.SpawningConfig.Count - 1)];
                Vector3 newSpawn = new Vector3(0f, 0f, 75f);

                World.RenderingCamera = null;
                if (_characterData.isDead)
                {
                    SpawnPosition.X = newSpawn.X;
                    SpawnPosition.Y = newSpawn.Y;
                    SpawnPosition.Z = newSpawn.Z;
                    SpawnHeading = new Random().Next(0, 359);

                    new Session(_characterData).InitializeSession();
                }
                else
                {
                    //SpawnPosition.X = _characterData.LastPos[0];
                    //SpawnPosition.Y = _characterData.LastPos[1];
                    //SpawnPosition.Z = _characterData.LastPos[2];
                    SpawnPosition.X = newSpawn.X;
                    SpawnPosition.Y = newSpawn.Y;
                    SpawnPosition.Z = newSpawn.Z;
                    SpawnHeading = new Random().Next(0, 359);

                    new Session(_characterData).InitializeSession();
                }
            }

            API.RequestCollisionAtCoord(SpawnPosition.X, SpawnPosition.Y, SpawnPosition.Z);

            API.SetEntityCoordsNoOffset(API.PlayerPedId(), SpawnPosition.X, SpawnPosition.Y, SpawnPosition.Z, false, false, false);

            API.ClearPedTasksImmediately(API.PlayerPedId());

            API.SetEntityCollision(API.PlayerPedId(), true, true);

            API.FreezeEntityPosition(API.PlayerPedId(), false);

            Game.Player.Character.Position = SpawnPosition;
            Game.Player.Character.Heading = SpawnHeading;

            Main.GetInstance().SetNuiFocus(false, false);
        }

This is the resource starting and enabling the NUI character screen to select from.

private async void ClientResourceStarted(string _resourceName)
        {
            if (API.GetCurrentResourceName() == _resourceName)
            {
                Main.GetInstance().SetNuiFocus(false, false);
                
                int maxThreshhold = 5000;
                int timeStamp = Game.GameTime;

                Game.Player.CanControlCharacter = false;
                Game.Player.IsInvincible = true;
                Screen.LoadingPrompt.Show("Loading User Interface", LoadingSpinnerType.RegularClockwise);
                do { await BaseScript.Delay(1000); } while ((!NUIReady) && ((timeStamp + maxThreshhold) > Game.GameTime));
                //do { await BaseScript.Delay(1000); } while (Game.PlayerPed == null);
                API.ShutdownLoadingScreenNui();
                API.ShutdownLoadingScreen();
                Screen.LoadingPrompt.Hide();
                Game.Player.CanControlCharacter = true;
                Game.Player.IsInvincible = false;
                
                Main.TriggerServerEvent("FiveRP:CreateSession");
            }
        }

        private async void EnableCharacterScreen(string _characters)
        {
            await BaseScript.Delay(0);
            Main.GetInstance().SetNuiFocus(true, true);
            Main.GetInstance().SendNUIData("fiverp_character", "OpenMenu", _characters);

            World.RenderingCamera = null;

            Screen.Hud.IsRadarVisible = false;
        }

The issue you’re describing sounds like an issue I had a while back when playing around with cameras. I found that if you “leave the player hanging” for too long (i.e. fade the screen out and leave it), the loading box in the bottom right automatically pops up, likely a builtin R* function to act as feedback for when something is taking too long to load.

In your case, the way you are describing the issue, my best guess is that you are either not recreating the camera correctly, or you’re failing to swap to it from the gameplay camera for whatever reason. I can’t tell from the code you’ve posted since there are only 2 references to cameras, but I would look through your code that had the camera logic in it and double-check it.

Feel free to cherrypick the parts with camera logic and post them here, I’ll try and give you a hand looking for the issue.

Alrighty so I have 5 mentions of ‘World.RenderingCamera’ within the framework, and I’ve posted two so I’ll post the next three here. There is two mentions in the first function.

This get’s triggered when the player has fully connected to the server and has selected a NEW character this is one of the first things to get triggered on the client if the character is new. For reference I am not using a ‘new’ character.

public static async void EnableCharacterModifier(Character _character)
        {
            // Character Setup
            CurrentCharacter = _character;
            World.RenderingCamera = World.CreateCamera(Game.Player.Character.Position, new Vector3(0f, 0f, 0f), 30f);
            await BaseScript.Delay(50);
            World.RenderingCamera.AttachTo(Game.Player.Character.Bones[Bone.SKEL_Head], new Vector3(0f, 2f, 0.5f));
            SetPedBlendData();
            CreateMenu();
        }

This is when the player has selected a NEW character and has also finished editing the characters features, so this only get’s used for new characters as well.

public static void DisableCharacterModifier()
        {
            Main.GetInstance().UnregisterTickHandler(KeepMenuEnabled);
            MenuController.Menus.Remove(Menu);
            World.RenderingCamera = null;
            //Initialize client session?
        }
World.RenderingCamera = World.CreateCamera(Game.Player.Character.Position, new Vector3(0f, 0f, 0f), 30f);

When you execute this line, the cameras .IsActive value is set to true, and RenderScriptCams() is called. However, when you set World.RenderingCamera to null, only RenderScriptCams() is called; the camera you created is never set to inactive.

I don’t know if that would make a difference or not, but it might be worth setting it inactive before turning it off. Maybe it’s staying active in the background or something, who knows.

World.RenderingCamera.IsActive = false;
World.RenderingCamera = null;

I added ‘IsActive = false’ but still get the same issue, I did it before every time I set the ‘RenderingCamera’ to null

So I did some debugging when the client resource started, and on a clients first connection the ‘World.RenderingCamera’ both ‘IsActive’ and ‘Exists()’ return true. But when the client connects the second time they are both false and I can not set it to true, and when trying to set the ‘RenderingCamera’ to a new ‘World.CreateCamera()’ it doesn’t work. Also the handle of the camera is -1.

I have the same exact problem described by the OP.
I removed some resources, including the mapmanager and the skater/hipster maps, and I’ve rewritten the code in C# in my gamemode project.
When I connect to my server, everything works as expected, however if I disconnect and then reconnect, the screen stays black.

My resources are

  • this .NET gamemode script
  • baseevents
  • chat
  • hardcap
  • playernames
  • rconlòg
  • runcode
  • scoreboard
  • sessionmanager

In my Tick() event I have:

            if (tickCount % 100 == 0)
            {
                var playerId = PlayerId();
                var pedId = PlayerPedId();

                // Spawning
                if (pedId != -1 && NetworkIsPlayerActive(playerId))
                {
                    if (PlayerState.ForceSkinSelection)
                    {
                        Log("Forcing the player to select a skin");
                        SkinSelector.EnterSkinSelection(); // code below
                        PlayerState.ForceSkinSelection = false;
                    }
                    else if (PlayerState.ForceRespawn 
                    || (deathTimestamp.HasValue && CheckTimePassed(deathTimestamp.Value, 8000)))  
                     // CheckTimePassed is a method that returns whether the specified amount 
                     // of ms have passed since the specified DateTime
                     // deathTimestamp is a Nullable<DateTime>
                    {
                        Log("Respawning the player");
                        Spawner.Spawn();
                        PlayerState.ForceRespawn = false;
                        deathTimestamp = null;
                    }
                    else if (deathTimestamp.HasValue && CheckTimePassed(deathTimestamp.Value, 4000))
                    {
                        SwitchOutPlayer(pedId, 0, 1);
                    }
                }
                
                if (IsEntityDead(pedId))
                {
                    if (!deathTimestamp.HasValue) 
                    {
                        deathTimestamp = DateTime.Now;
                        PlayerState.IsSpawned = false;
                        Log("Player died.");
                    }
                }
                else
                {
                    deathTimestamp = null;
                }
            }

Relevant parts of SkinSelector


        //
        public static void EnterSkinSelection()
        {
            int playerId = PlayerId();
            int pedId = PlayerPedId();

            // freeze and hide the player ped during the loading
            Log("Freezing and hiding the player ped.");
            PlayerActions.Freeze(true); // code below
            PlayerActions.Hide(true); // code below

            // set to the first skin in the list
            uint model = (uint)(CivilianSkins[0].PedHash); // CivilianSkins is a list of "Skin" objects, each containing a name and a PedHash
            Log($"Setting player model ({CivilianSkins[0]})");
            SetPlayerModel(playerId, model);
            Log("Model was set");

            //
            Log("Requesting collision");
            RequestCollisionAtCoord(pedPosition.X, pedPosition.Y, pedPosition.Z);

            Log("Setting position");
            SetEntityCoordsNoOffset(pedId, pedPosition.X, pedPosition.Y, pedPosition.Z, false, false, false);
            NetworkResurrectLocalPlayer(pedPosition.X, pedPosition.Y, pedPosition.Z, pedPosition.W, true, true);
            ResurrectPed(pedId);

            Log("Spawned");
            ClearPedTasksImmediately(pedId);
            RemoveAllPedWeapons(pedId, false);
            ClearPlayerWantedLevel(playerId);

            var t = DateTime.Now;
            while (!HasCollisionLoadedAroundEntity(pedId) && !CheckTimePassed(t, 5000))
                Wait(100);

            ShutdownLoadingScreen();

            // create the selection camera
            Log("Creating skin selection camera...");
            camera = new Camera(CreateCam("DEFAULT_SCRIPTED_CAMERA", false));
            camera.Position = camPosition;
            camera.PointAt(new Vector3(pedPosition.X, pedPosition.Y, pedPosition.Z + 1f));
            camera.IsActive = true;
            RenderScriptCams(true, false, 0, true, false);
            SetCamAffectsAiming(camera.Handle, true);
            Log("    Camera created and activated");

            //
            PlayerActions.Hide(false);

            // create the menu
            menu = new Menu("Skin Selection", "Select your character skin");

            foreach (var skin in CivilianSkins)
            {
                var item = new MenuItem(skin.Name);
                item.ItemData = skin;
                menu.AddMenuItem(item);
            }

            menu.OnMenuClose += OnMenuClose;
            menu.OnIndexChange += OnMenuIndexChange;
            menu.OnItemSelect += OnMenuItemSelect;

            MenuController.AddMenu(menu);
            MenuController.MenuAlignment = MenuController.MenuAlignmentOption.Right;
            MenuController.MainMenu = menu;

            menu.Visible = true;

            //
            Log("Skin selection is ready");
        }

        private static void OnMenuClose(Menu menu)
        {
            menu.Visible = true;
        }

        //
        private static void OnMenuIndexChange(Menu menu, MenuItem oldItem, MenuItem newItem, int oldIndex, int newIndex)
        {
            if (newItem.ItemData is Skin skin)
            {
                int playerId = PlayerId();
                uint model = (uint)(skin.PedHash);
                Log($"Setting player model ({skin})");
                SetPlayerModel(playerId, model);
            }
        }

        //
        private static void OnMenuItemSelect(Menu menu, MenuItem menuItem, int itemIndex)
        {
            if (camera != null)
            {
                RenderScriptCams(false, false, 0, true, false);
                camera.Delete();
                camera = null;
            }

            string description = "Skin";
            if (menuItem.ItemData is Skin skin)
            {
                description = skin.ToString();
            }

            PlayerActions.Freeze(false);
            DisplaySubtitle($"You selected ~r~{description}~s~!", 2000);
            menu.Visible = false;

            SwitchOutPlayer(PlayerPedId(), 0, 1);

            Spawner.Spawn();
        }

Freeze() and Hide()

        public static void Freeze(bool freeze)
        {
            var player = PlayerId();
            var ped = GetPlayerPed(player);

            SetPlayerControl(player, !freeze, 0);
            SetPlayerInvincible(player, freeze);
            FreezeEntityPosition(ped, freeze);

            if (freeze)
            {
                if (!IsPedFatallyInjured(ped))
                    ClearPedTasksImmediately(ped);
            }
        }

        public static void Hide(bool hide)
        {
            var player = PlayerId();
            var ped = GetPlayerPed(player);

            SetEntityVisible(ped, !hide, true);
            SetEntityCollision(ped, !hide, !hide);
        }

Spawner.Spawn()

        public static void Spawn()
        {
            //
            int playerId = PlayerId();
            int pedId = PlayerPedId();

            //
            Log($"Getting a random spawn point (count is {SpawnPoints.Count})");
            var rIdx = GetRandom(SpawnPoints.Count);
            Log($"rIdx is {rIdx}");

            Vector4 spawn = SpawnPoints[rIdx];
            Log($"Spawn: {spawn.X}, {spawn.Y}, {spawn.Z}, {spawn.W}");

            Log("Requesting collision");
            RequestCollisionAtCoord(spawn.X, spawn.Y, spawn.Z);

            Log("Setting position");
            SetEntityCoordsNoOffset(pedId, spawn.X, spawn.Y, spawn.Z, false, false, false);
            NetworkResurrectLocalPlayer(spawn.X, spawn.Y, spawn.Z, spawn.W, true, true);
            ResurrectPed(pedId);

            Log("Spawned");

            ClearPedTasksImmediately(pedId);
            RemoveAllPedWeapons(pedId, false);
            ClearPlayerWantedLevel(playerId);

            Log("Switching in");
            SwitchInPlayer(pedId);

            PlayerState.IsSpawned = true;
        }

In all scripts I have using static CitizenFX.Core.Native.API; to avoid having to type API. before every API call.

Still having this issue, anyone got an idea to what may be the cause or how to fix it?

I realized there is no way, possibly it’s a FiveM client issue. The only way is to close FiveM completely and then reopen it.

How, when the built-in (not meant to be user-replaceable, darnit! stop acting like it is and start using spawnmanager’s API) spawn logic works fine?

Yea I don’t think it’s a FiveM client issue, unless it’s a C# specific issue. Since ESX and all the rest of the frameworks don’t have this issue.
Originally I didn’t have this issue, then I did some work on the spawn manager and then the issue started after. So it’s gotta be something to do with mishandling some camera data or something.

Sorry, I don’t mean to offend you or FiveM, but spawnmanager and mapmanager are not suitable for all use cases. The code you see in my previous example has now became even more specific to my gamemode, and more complicated.

Anyway, I doubt replacing spawnmanager is the issue, because the problem happens before even showing the default view (the bridge): the screen stays black right after the loading screen.

I explicitly said spawnmanager, where did I mention mapmanager?

If spawnmanager’s API isn’t ‘suitable for your use case’, make a PR adding additional API fields. It is not intended to be user-serviceable and is only implemented as a resource since implementing it in C++ would’ve been weird - same goes for chat.

Nope, not related, ‘the default view (the bridge)’ is skipped for game reloads, so this is user code causing problems anyway, like for example your replacement for spawnmanager forgetting to do something that spawnmanager does do but only works if the placeholder scene is rendered before.

Could I chuck my spawn manager up for you to go over and check if it’s missing anything crucial?

EDIT: Not too sure what I’ve done, maybe it was NUI callbacks being null and causing network errors or something. But issue doesn’t happen anymore.