17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 22:39:19 +02:00
Files
maszyna/network/entities.cpp
Hirek193 b9bd0ee33d 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.
2026-09-06 17:48:35 +00:00

149 lines
3.8 KiB
C++

/*
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 "network/entities.h"
#include "simulation/simulation.h"
#include "vehicle/DynObj.h"
#include "vehicle/Driver.h"
#include "vehicle/Train.h"
#include "utilities/Logs.h"
namespace network
{
entity_registry Entities;
PeerId allocate_peer_id()
{
static PeerId next = PEER_FIRST_REMOTE;
return next++;
}
bool is_drivable(TDynamicObject const *Vehicle)
{
if (Vehicle == nullptr || Vehicle->MoverParameters == nullptr)
return false;
// anything with its own drive, plus anything the scenario already put a driver in
// (this covers driving trailers of multiple units, which carry no engine of their own)
// TODO: a cab count exposed by TMoverParameters would be a more precise test
return Vehicle->MoverParameters->EngineType != TEngineType::None || Vehicle->Mechanik != nullptr;
}
void entity_registry::clear()
{
m_entries.clear();
m_byname.clear();
m_byid.clear();
}
void entity_registry::build()
{
clear();
std::vector<std::string> names;
for (TDynamicObject *vehicle : simulation::Vehicles.sequence())
{
if (!is_drivable(vehicle))
continue;
names.emplace_back(vehicle->name());
}
// name order keeps the numbering reproducible regardless of scenario spawn order
std::sort(names.begin(), names.end());
for (auto const &name : names)
{
if (m_byname.find(name) != m_byname.end())
{
// a malformed scenario can hold duplicate names; only the first one is addressable
ErrorLog("net: duplicate vehicle name \"" + name + "\", not assigning a second network id", logtype::net);
continue;
}
vehicle_entry entry;
entry.id = static_cast<NetworkEntityId>(m_entries.size() + 1);
entry.name = name;
m_entries.emplace_back(entry);
m_byname.emplace(name, entry.id);
}
reindex();
refresh();
WriteLog("net: assigned network ids to " + std::to_string(m_entries.size()) + " drivable vehicles", logtype::net);
}
void entity_registry::adopt(std::vector<vehicle_entry> const &Entries)
{
m_entries = Entries;
m_byname.clear();
for (auto const &entry : m_entries)
m_byname.emplace(entry.name, entry.id);
reindex();
}
void entity_registry::reindex()
{
m_byid.clear();
for (size_t i = 0; i < m_entries.size(); ++i)
m_byid.emplace(m_entries[i].id, i);
}
void entity_registry::refresh()
{
for (auto &entry : m_entries)
{
TDynamicObject const *vehicle = resolve(entry.id);
entry.ai_active = (vehicle != nullptr && vehicle->Mechanik != nullptr && vehicle->Mechanik->AIControllFlag);
// until the crew model of stage 3 is in place, an existing cab instance is the
// only evidence that somebody took the vehicle over
entry.crew_count = (simulation::Trains.find(entry.name) != nullptr ? 1 : 0);
entry.crew_capacity = CREW_CAPACITY;
entry.claimable = (vehicle != nullptr && entry.crew_count < entry.crew_capacity);
}
}
NetworkEntityId entity_registry::id_of(std::string const &Name) const
{
auto const lookup = m_byname.find(Name);
return lookup != m_byname.end() ? lookup->second : ENTITY_NONE;
}
NetworkEntityId entity_registry::id_of(TDynamicObject const *Vehicle) const
{
return Vehicle != nullptr ? id_of(Vehicle->name()) : ENTITY_NONE;
}
std::string entity_registry::name_of(NetworkEntityId Id) const
{
auto const *entry = find(Id);
return entry != nullptr ? entry->name : std::string();
}
vehicle_entry const *entity_registry::find(NetworkEntityId Id) const
{
auto const lookup = m_byid.find(Id);
return lookup != m_byid.end() ? &m_entries[lookup->second] : nullptr;
}
TDynamicObject *entity_registry::resolve(NetworkEntityId Id) const
{
auto const *entry = find(Id);
if (entry == nullptr)
return nullptr;
return simulation::Vehicles.find(entry->name);
}
} // namespace network