17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 03:19:19 +02:00

multiplayer stage 2: peer identity, stable vehicle ids and a session lobby

TTrain::id() numbers cabs with a local counter, in the order each peer happens
to create them, so it cannot address a vehicle across the session. Neither was
there any way for a player to see what is available to drive.

- network/entities: PeerId (the host is peer 1, remote peers are numbered from
  2, so it is a participant rather than a special case) and NetworkEntityId,
  plus a registry that numbers the drivable vehicles of the loaded scenario in
  name order and resolves ids back to TDynamicObject
- the server owns the numbering and publishes it as VEHICLE_LIST - name, crew
  occupancy, crew capacity, AI state, availability - on every change and at
  least every 120 frames, out of band so it stays out of the replay backbuffer;
  a client binds the received ids to its own copy of the scenario by name
- SERVER_HELLO hands the joining peer its PeerId
- a multiplayer lobby listing that roster, with vehicles taken by other players
  shown as such rather than hidden; entering one goes through the existing
  entervehicle command, the vehicle is never moved

The lobby lives inside the driver mode instead of a mode of its own: the world
has to keep running while somebody is choosing (a client stops consuming the
server's frames the moment its mode is not a command processor), and the same
screen is what a player needs to change vehicles later on.

Crew occupancy is still derived from the presence of a cab instance; the real
crew model, claim handshake and AI takeover follow in stage 3.
This commit is contained in:
2026-09-06 17:48:35 +00:00
parent 8460fc531f
commit b9bd0ee33d
17 changed files with 573 additions and 7 deletions

View File

@@ -0,0 +1,119 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "widgets/multiplayer_lobby.h"
#include "application/application.h"
#include "network/entities.h"
#include "simulation/simulation.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
#include "utilities/translation.h"
#include "vehicle/Driver.h"
#include "vehicle/DynObj.h"
#include "vehicle/Train.h"
ui::multiplayer_lobby_panel::multiplayer_lobby_panel()
: ui_panel(STR_C("Multiplayer lobby"), false)
{
size_min = {480, 260};
}
void ui::multiplayer_lobby_panel::enter_vehicle(std::string const &Name)
{
TDynamicObject *vehicle = simulation::Vehicles.find(Name);
if (vehicle == nullptr)
return;
std::string payload{Name};
// the request travels as an ordinary simulation command, so every peer builds the cab
m_relay.post(user_command::entervehicle, 0.0, simulation::Train ? simulation::Train->id() : 0, GLFW_PRESS, 0, vehicle->GetPosition(), &payload);
// ...and the driver mode moves our own camera in once the cab shows up locally
Global.network_pending_vehicle = Name;
is_open = false;
}
void ui::multiplayer_lobby_panel::render_contents()
{
bool const host{Application.is_server()};
bool const client{Application.is_client()};
if (!host && !client)
{
ImGui::TextUnformatted(STR_C("This is not a multiplayer session."));
return;
}
ImGui::Text("%s: %s, peer %u", STR_C("Session"), host ? (client ? "host + client" : "host") : "client", Global.network_peer_id);
ImGui::TextUnformatted(Global.SceneryFile.c_str());
ImGui::Separator();
auto const &entries = network::Entities.entries();
if (entries.empty())
{
ImGui::TextUnformatted(STR_C("Waiting for the vehicle list from the server..."));
return;
}
std::string const currentvehicle{simulation::Train != nullptr && simulation::Train->Dynamic() != nullptr ? simulation::Train->Dynamic()->name() : std::string()};
if (!FreeFlyModeFlag)
{
ImGui::TextUnformatted(STR_C("Leave the cab to change vehicles."));
ImGui::Separator();
}
ImGui::Columns(4, "mp_vehicles", false);
ImGui::SetColumnWidth(1, 80.0f * Global.ui_scale);
ImGui::SetColumnWidth(2, 90.0f * Global.ui_scale);
ImGui::SetColumnWidth(3, 130.0f * Global.ui_scale);
ImGui::TextDisabled("%s", STR_C("Vehicle"));
ImGui::NextColumn();
ImGui::TextDisabled("%s", STR_C("Crew"));
ImGui::NextColumn();
ImGui::TextDisabled("%s", STR_C("Driver"));
ImGui::NextColumn();
ImGui::NextColumn();
ImGui::Separator();
for (auto const &entry : entries)
{
ImGui::PushID(static_cast<int>(entry.id));
ImGui::TextUnformatted(entry.name.c_str());
ImGui::NextColumn();
ImGui::Text("%u/%u", (unsigned)entry.crew_count, (unsigned)entry.crew_capacity);
ImGui::NextColumn();
ImGui::TextUnformatted(entry.crew_count > 0 ? STR_C("player") : entry.ai_active ? STR_C("AI") : STR_C("free"));
ImGui::NextColumn();
if (entry.name == currentvehicle)
{
ImGui::TextUnformatted(STR_C("you"));
}
else if (entry.claimable && FreeFlyModeFlag && network::Entities.resolve(entry.id) != nullptr)
{
if (ImGui::Button(STR_C("Enter"), ImVec2(-1, 0)))
enter_vehicle(entry.name);
}
else
{
ImGui::TextDisabled("%s", entry.claimable ? "-" : STR_C("occupied"));
}
ImGui::NextColumn();
ImGui::PopID();
}
ImGui::Columns(1);
}