17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 06:49:18 +02:00
Files
maszyna/network/network.h
Hirek193 3f62fd5927 multiplayer stage 3: crew model, claim handshake and AI handover
A vehicle is not owned by one player: two people share a cab, so the session
needs a crew relation rather than an owner field. The server keeps it and is
the only one allowed to change it.

- network/session: vehicle_crew { entity_id, crew, ai_before_claim } and a
  crew_registry holding the roster. the host goes through exactly the same
  path as a remote peer (claim -> crew -> grant), only without transport
- CLAIM_VEHICLE / CLAIM_GRANTED / CLAIM_DENIED / LEAVE_VEHICLE / CREW_UPDATE;
  the server validates the vehicle exists, is present in the scenario and has
  room in the crew, and answers a refusal with a reason the lobby shows
- the cab of a claimed vehicle is created by the authority posting the
  ordinary replicated entervehicle, so every peer builds it and the vehicle is
  never moved. the claiming peer only walks in once the cab appears locally
- AI transitions follow the crew size: 0 -> 1 remembers whether an AI was
  really driving and takes the vehicle over, 1 -> 2 and 2 -> 1 change nothing,
  1 -> 0 hands the vehicle back but only if there was an AI driver to begin
  with. driver mode no longer fires aidriverdisable of its own in multiplayer
- losing a connection removes that peer from every crew it was on; a vehicle
  with somebody still on board carries on untouched
- the lobby lists who is on board, offers "join crew" for a vehicle another
  player already occupies, and lets a player step off

The AI and cab handling runs as a reconciliation pass rather than as edge
triggered actions, so a request that has not taken effect yet (the cab needs a
frame to appear) is simply repeated, with a cooldown to keep it off the wire.
2026-09-06 18:08:06 +00:00

137 lines
3.7 KiB
C++

#pragma once
#include <memory>
#include <functional>
#include <optional>
#include <queue>
#include <chrono>
#include "network/message.h"
#include "network/entities.h"
#include "network/session.h"
#include "input/command.h"
namespace network
{
//m7todo: separate client/server connection class?
class connection
{
friend class server;
friend class client;
private:
const int CATCHUP_PACKETS = 300;
bool is_client;
protected:
std::shared_ptr<std::istream> backbuffer;
size_t backbuffer_pos;
size_t packet_counter;
void send_complete(std::shared_ptr<std::string> buf);
void catch_up();
public:
std::function<void(const message &msg)> message_handler;
virtual void connected() = 0;
virtual void send_message(const message &msg) = 0;
virtual void send_messages(const std::vector<std::shared_ptr<message>> &messages) = 0;
connection(bool client = false, size_t counter = 0);
void set_handler(std::function<void(const message &msg)> handler);
virtual void disconnect() = 0;
enum peer_state {
AWAITING_HELLO,
CATCHING_UP,
ACTIVE,
DEAD
};
peer_state state;
// session identity of the peer on the other side of this connection
PeerId peer_id { PEER_NONE };
};
class server
{
private:
std::shared_ptr<std::istream> backbuffer;
protected:
void handle_message(std::shared_ptr<connection> conn, const message &msg);
std::vector<std::shared_ptr<connection>> clients;
command_queue::commands_map client_commands_queue;
protected:
// drops peers whose socket is gone, together with their crew memberships
void prune_clients();
public:
server(std::shared_ptr<std::istream> buf);
void push_delta(const frame_info &msg);
// sends an out of band message to every active peer. unlike push_delta this is not
// written to the backbuffer, so it does not become part of the replayed history
void push_message(const message &msg);
command_queue::commands_map pop_commands();
};
class client
{
protected:
virtual void connect() = 0;
void handle_message(std::shared_ptr<connection> conn, const message &msg);
std::shared_ptr<connection> conn;
size_t resume_frame_counter = 0;
size_t reconnect_delay = 0;
const size_t RECONNECT_DELAY_FRAMES = 60;
const float MAX_BUFFER_SIZE = 60.0f;
const float JITTERINESS_MIX = 0.998f;
const float TARGET_MIN = 2.0f;
const float TARGET_MIX = 0.98f;
const float JITTERINESS_MULTIPIER = 2.0f;
const float CONSUME_MULTIPIER = 0.05f;
std::queue<frame_info> delta_queue;
float last_target = 20.0f;
float jitteriness = 1.0f;
float consume_counter = 0.0f;
std::chrono::high_resolution_clock::time_point last_rcv;
std::chrono::high_resolution_clock::time_point last_frame;
std::chrono::high_resolution_clock::duration frame_time;
public:
void update();
std::tuple<double, double, command_queue::commands_map> get_next_delta(int counter);
void send_commands(command_queue::commands_map commands);
// lobby requests; the server is the one that decides
void send_claim(NetworkEntityId entity_id);
void send_leave(NetworkEntityId entity_id);
int get_frame_counter() {
return resume_frame_counter;
}
int get_awaiting_frames() {
return delta_queue.size();
}
};
class backend_manager
{
public:
virtual std::shared_ptr<server> create_server(std::shared_ptr<std::fstream>, const std::string &conf) = 0;
virtual std::shared_ptr<client> create_client(const std::string &conf) = 0;
virtual void update() = 0;
};
// HACK: static initialization order fiasco fix
// extern std::unordered_map<std::string, backend_manager*> backend_list;
using backend_list_t = std::unordered_map<std::string, backend_manager*>;
backend_list_t& backend_list();
}