From a72c3292adb52f3543d3fb3d72ccfc9aab5d5843 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Sun, 6 Sep 2026 22:54:48 +0000 Subject: [PATCH] multiplayer: fix the black screen - a cab and is_ready waiting on each other The renderer draws nothing at all while simulation::is_ready is false, and that flag only goes up once the local player's cab is built. The previous commit made walking into a cab a crew request, so the cab of a claimed vehicle is created by Crews::reconcile() rather than by the command itself. reconcile() runs from the network housekeeping, which was gated on simulation::is_ready - so a host started with -v had its entervehicle turned into a claim, and then the cab waited for is_ready while is_ready waited for the cab. Black screen on both ends, since a client whose server never gets going has nothing to show either. - the housekeeping now waits for Global.simulation_loaded, which says the world exists, instead of simulation::is_ready, which says the player has a cab - in a session, a player with no cab counts as ready: they are an observer in the lobby, not a game still loading, and the world has to be drawn for them - entervehicle is only taken as a crew request when the session actually lists that vehicle; anything else stays an ordinary command, so a scenario that starts its host in something the roster does not cover still works --- application/drivermode.cpp | 7 ++++++- application/scenarioloadermode.cpp | 4 ++++ network/manager.cpp | 5 ++++- network/session.cpp | 6 ++++-- utilities/Globals.h | 3 +++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/application/drivermode.cpp b/application/drivermode.cpp index e41eb077..daacf7a0 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Timer.h" #include "rendering/renderer.h" #include "utilities/Logs.h" +#include "network/entities.h" /* namespace input { @@ -454,7 +455,11 @@ bool driver_mode::update() GfxRenderer->Update(deltarealtime); - simulation::is_ready = simulation::is_ready || (simulation::Train != nullptr && simulation::Train->is_cab_initialized) || Global.local_start_vehicle == "ghostview"; + simulation::is_ready = simulation::is_ready || (simulation::Train != nullptr && simulation::Train->is_cab_initialized) || Global.local_start_vehicle == "ghostview" + // in a session a player with no cab is an observer waiting in the + // lobby, not a game still loading: the world has to be drawn for + // them, or they are left staring at a black screen + || (network::is_multiplayer() && simulation::Train == nullptr); return true; } diff --git a/application/scenarioloadermode.cpp b/application/scenarioloadermode.cpp index 05af181d..2c28097c 100644 --- a/application/scenarioloadermode.cpp +++ b/application/scenarioloadermode.cpp @@ -62,6 +62,10 @@ bool scenarioloader_mode::update() { Application.pop_mode(); } + // the world exists from here on. this is what the network housekeeping waits for; + // simulation::is_ready means something else, namely that the player has a cab + Global.simulation_loaded = true; + if( Application.is_server() ) { // the server owns the vehicle numbering for the whole session and publishes it network::Entities.build(); diff --git a/network/manager.cpp b/network/manager.cpp index 89c50c38..a988bca0 100644 --- a/network/manager.cpp +++ b/network/manager.cpp @@ -194,7 +194,10 @@ void network::manager::update() for (auto &backend : backend_list()) backend.second->update(); - if (servers && simulation::is_ready) { + if (servers && Global.simulation_loaded) { + // note: not simulation::is_ready. that one waits for the local player's cab, and + // the cab of a claimed vehicle is built by update_crews() - waiting for it here + // would leave the two waiting on each other servers->update_crews(); servers->publish_vehicle_list(); servers->publish_state(); diff --git a/network/session.cpp b/network/session.cpp index 1fe15c82..75200076 100644 --- a/network/session.cpp +++ b/network/session.cpp @@ -120,10 +120,12 @@ void filter_commands(PeerId Peer, command_queue::commands_map &Commands) continue; } - if (command->command == user_command::entervehicle) + if ((command->command == user_command::entervehicle) && (Entities.id_of(command->payload) != ENTITY_NONE)) { // the local player walked into a cab. that is a crew request; the cab - // itself is built by the authority once the seat is granted + // itself is built by the authority once the seat is granted. + // a vehicle the session does not list falls through to the ordinary path, + // so a scenario that starts its host in something unusual still works NetworkEntityId entity{ENTITY_NONE}; auto const result = claim_by_name(Peer, command->payload, entity); if (result != claim_result::granted && result != claim_result::already_member) diff --git a/utilities/Globals.h b/utilities/Globals.h index 1da6f56b..23394a94 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -372,6 +372,9 @@ struct global_settings { uint64_t network_session_token = 0; // how far the worst vehicle was out of place when the last correction arrived, metres float network_position_error = 0.0f; + // the scenario has finished loading and the world can be worked with. this is not the + // same as simulation::is_ready, which only goes up once the player has a cab + bool simulation_loaded = false; std::unordered_map trainset_overrides;