|
|
|
|
#include "dvlnet/base.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
namespace net {
|
|
|
|
|
|
|
|
|
|
void base::setup_gameinfo(buffer_t info)
|
|
|
|
|
{
|
|
|
|
|
game_init_info = std::move(info);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::setup_password(std::string pw)
|
|
|
|
|
{
|
|
|
|
|
pktfty = std::make_unique<packet_factory>(pw);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::clear_password()
|
|
|
|
|
{
|
|
|
|
|
pktfty = std::make_unique<packet_factory>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::RunEventHandler(_SNETEVENT &ev)
|
|
|
|
|
{
|
|
|
|
|
auto f = registered_handlers[static_cast<event_type>(ev.eventid)];
|
|
|
|
|
if (f != nullptr) {
|
|
|
|
|
f(&ev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::DisconnectNet(plr_t plr)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::SendEchoRequest(plr_t player)
|
|
|
|
|
{
|
|
|
|
|
if (plr_self == PLR_BROADCAST)
|
|
|
|
|
return;
|
|
|
|
|
if (player == plr_self)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
timestamp_t now = SDL_GetTicks();
|
|
|
|
|
auto echo = pktfty->make_packet<PT_ECHO_REQUEST>(plr_self, player, now);
|
|
|
|
|
send(*echo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::HandleAccept(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
if (plr_self != PLR_BROADCAST) {
|
|
|
|
|
return; // already have player id
|
|
|
|
|
}
|
|
|
|
|
if (pkt.Cookie() == cookie_self) {
|
|
|
|
|
plr_self = pkt.NewPlayer();
|
|
|
|
|
Connect(plr_self);
|
|
|
|
|
}
|
|
|
|
|
if (game_init_info != pkt.Info()) {
|
|
|
|
|
if (pkt.Info().size() != sizeof(GameData)) {
|
|
|
|
|
ABORT();
|
|
|
|
|
}
|
|
|
|
|
// we joined and did not create
|
|
|
|
|
game_init_info = pkt.Info();
|
|
|
|
|
_SNETEVENT ev;
|
|
|
|
|
ev.eventid = EVENT_TYPE_PLAYER_CREATE_GAME;
|
|
|
|
|
ev.playerid = plr_self;
|
|
|
|
|
ev.data = const_cast<unsigned char *>(pkt.Info().data());
|
|
|
|
|
ev.databytes = pkt.Info().size();
|
|
|
|
|
RunEventHandler(ev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::HandleConnect(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
plr_t newPlayer = pkt.NewPlayer();
|
|
|
|
|
Connect(newPlayer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::HandleTurn(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
plr_t src = pkt.Source();
|
|
|
|
|
PlayerState &playerState = playerStateTable_[src];
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
const turn_t &turn = pkt.Turn();
|
|
|
|
|
turnQueue.push_back(turn);
|
|
|
|
|
MakeReady(turn.SequenceNumber);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::HandleDisconnect(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
plr_t newPlayer = pkt.NewPlayer();
|
|
|
|
|
if (newPlayer != plr_self) {
|
|
|
|
|
if (IsConnected(newPlayer)) {
|
|
|
|
|
auto leaveinfo = pkt.LeaveInfo();
|
|
|
|
|
_SNETEVENT ev;
|
|
|
|
|
ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME;
|
|
|
|
|
ev.playerid = pkt.NewPlayer();
|
|
|
|
|
ev.data = reinterpret_cast<unsigned char *>(&leaveinfo);
|
|
|
|
|
ev.databytes = sizeof(leaveinfo_t);
|
|
|
|
|
RunEventHandler(ev);
|
|
|
|
|
DisconnectNet(newPlayer);
|
|
|
|
|
ClearMsg(newPlayer);
|
|
|
|
|
PlayerState &playerState = playerStateTable_[newPlayer];
|
|
|
|
|
playerState.isConnected = false;
|
|
|
|
|
playerState.turnQueue.clear();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ABORT(); // we were dropped by the owner?!?
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::HandleEchoRequest(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
auto reply = pktfty->make_packet<PT_ECHO_REPLY>(plr_self, pkt.Source(), pkt.Time());
|
|
|
|
|
send(*reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::HandleEchoReply(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
uint32_t now = SDL_GetTicks();
|
|
|
|
|
plr_t src = pkt.Source();
|
|
|
|
|
PlayerState &playerState = playerStateTable_[src];
|
|
|
|
|
playerState.roundTripLatency = now - pkt.Time();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::ClearMsg(plr_t plr)
|
|
|
|
|
{
|
|
|
|
|
message_queue.erase(std::remove_if(message_queue.begin(),
|
|
|
|
|
message_queue.end(),
|
|
|
|
|
[&](message_t &msg) {
|
|
|
|
|
return msg.sender == plr;
|
|
|
|
|
}),
|
|
|
|
|
message_queue.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::Connect(plr_t player)
|
|
|
|
|
{
|
|
|
|
|
PlayerState &playerState = playerStateTable_[player];
|
|
|
|
|
bool wasConnected = playerState.isConnected;
|
|
|
|
|
playerState.isConnected = true;
|
|
|
|
|
|
|
|
|
|
if (!wasConnected)
|
|
|
|
|
SendFirstTurnIfReady(player);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::IsConnected(plr_t player) const
|
|
|
|
|
{
|
|
|
|
|
const PlayerState &playerState = playerStateTable_[player];
|
|
|
|
|
return playerState.isConnected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::RecvLocal(packet &pkt)
|
|
|
|
|
{
|
|
|
|
|
if (pkt.Source() < MAX_PLRS) {
|
|
|
|
|
Connect(pkt.Source());
|
|
|
|
|
}
|
|
|
|
|
switch (pkt.Type()) {
|
|
|
|
|
case PT_MESSAGE:
|
|
|
|
|
message_queue.emplace_back(pkt.Source(), pkt.Message());
|
|
|
|
|
break;
|
|
|
|
|
case PT_TURN:
|
|
|
|
|
HandleTurn(pkt);
|
|
|
|
|
break;
|
|
|
|
|
case PT_JOIN_ACCEPT:
|
|
|
|
|
HandleAccept(pkt);
|
|
|
|
|
break;
|
|
|
|
|
case PT_CONNECT:
|
|
|
|
|
HandleConnect(pkt);
|
|
|
|
|
break;
|
|
|
|
|
case PT_DISCONNECT:
|
|
|
|
|
HandleDisconnect(pkt);
|
|
|
|
|
break;
|
|
|
|
|
case PT_ECHO_REQUEST:
|
|
|
|
|
HandleEchoRequest(pkt);
|
|
|
|
|
break;
|
|
|
|
|
case PT_ECHO_REPLY:
|
|
|
|
|
HandleEchoReply(pkt);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
// otherwise drop
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetReceiveMessage(int *sender, void **data, uint32_t *size)
|
|
|
|
|
{
|
|
|
|
|
poll();
|
|
|
|
|
if (message_queue.empty())
|
|
|
|
|
return false;
|
|
|
|
|
message_last = message_queue.front();
|
|
|
|
|
message_queue.pop_front();
|
|
|
|
|
*sender = message_last.sender;
|
|
|
|
|
*size = message_last.payload.size();
|
|
|
|
|
*data = message_last.payload.data();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetSendMessage(int playerId, void *data, unsigned int size)
|
|
|
|
|
{
|
|
|
|
|
if (playerId != SNPLAYER_ALL && playerId != SNPLAYER_OTHERS
|
|
|
|
|
&& (playerId < 0 || playerId >= MAX_PLRS))
|
|
|
|
|
abort();
|
|
|
|
|
auto *rawMessage = reinterpret_cast<unsigned char *>(data);
|
|
|
|
|
buffer_t message(rawMessage, rawMessage + size);
|
|
|
|
|
if (playerId == plr_self || playerId == SNPLAYER_ALL)
|
|
|
|
|
message_queue.emplace_back(plr_self, message);
|
|
|
|
|
plr_t dest;
|
|
|
|
|
if (playerId == SNPLAYER_ALL || playerId == SNPLAYER_OTHERS)
|
|
|
|
|
dest = PLR_BROADCAST;
|
|
|
|
|
else
|
|
|
|
|
dest = playerId;
|
|
|
|
|
if (dest != plr_self) {
|
|
|
|
|
auto pkt = pktfty->make_packet<PT_MESSAGE>(plr_self, dest, message);
|
|
|
|
|
send(*pkt);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::AllTurnsArrived()
|
|
|
|
|
{
|
|
|
|
|
for (auto i = 0; i < MAX_PLRS; ++i) {
|
|
|
|
|
PlayerState &playerState = playerStateTable_[i];
|
|
|
|
|
if (!playerState.isConnected)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
if (turnQueue.empty()) {
|
|
|
|
|
LogDebug("Turn missing from player {}", i);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetReceiveTurns(char **data, size_t *size, uint32_t *status)
|
|
|
|
|
{
|
|
|
|
|
poll();
|
|
|
|
|
|
|
|
|
|
for (auto i = 0; i < MAX_PLRS; ++i) {
|
|
|
|
|
status[i] = 0;
|
|
|
|
|
|
|
|
|
|
PlayerState &playerState = playerStateTable_[i];
|
|
|
|
|
if (!playerState.isConnected)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
status[i] |= PS_CONNECTED;
|
|
|
|
|
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
while (!turnQueue.empty()) {
|
|
|
|
|
const turn_t &turn = turnQueue.front();
|
|
|
|
|
seq_t diff = turn.SequenceNumber - next_turn;
|
|
|
|
|
if (diff <= 0x7F)
|
|
|
|
|
break;
|
|
|
|
|
turnQueue.pop_front();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (AllTurnsArrived()) {
|
|
|
|
|
for (auto i = 0; i < MAX_PLRS; ++i) {
|
|
|
|
|
PlayerState &playerState = playerStateTable_[i];
|
|
|
|
|
if (!playerState.isConnected)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
if (turnQueue.empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const turn_t &turn = turnQueue.front();
|
|
|
|
|
if (turn.SequenceNumber != next_turn)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
playerState.lastTurnValue = turn.Value;
|
|
|
|
|
turnQueue.pop_front();
|
|
|
|
|
|
|
|
|
|
size[i] = sizeof(int32_t);
|
|
|
|
|
status[i] |= PS_ACTIVE;
|
|
|
|
|
status[i] |= PS_TURN_ARRIVED;
|
|
|
|
|
data[i] = reinterpret_cast<char *>(&playerState.lastTurnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next_turn++;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto i = 0; i < MAX_PLRS; ++i) {
|
|
|
|
|
PlayerState &playerState = playerStateTable_[i];
|
|
|
|
|
if (!playerState.isConnected)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
if (turnQueue.empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
status[i] |= PS_ACTIVE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetSendTurn(char *data, unsigned int size)
|
|
|
|
|
{
|
|
|
|
|
if (size != sizeof(int32_t))
|
|
|
|
|
ABORT();
|
|
|
|
|
|
|
|
|
|
turn_t turn;
|
|
|
|
|
turn.SequenceNumber = next_turn;
|
|
|
|
|
std::memcpy(&turn.Value, data, size);
|
|
|
|
|
|
|
|
|
|
PlayerState &playerState = playerStateTable_[plr_self];
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
turnQueue.push_back(turn);
|
|
|
|
|
SendTurnIfReady(turn);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::SendTurnIfReady(turn_t turn)
|
|
|
|
|
{
|
|
|
|
|
if (awaitingSequenceNumber_)
|
|
|
|
|
awaitingSequenceNumber_ = !IsGameHost();
|
|
|
|
|
|
|
|
|
|
if (!awaitingSequenceNumber_) {
|
|
|
|
|
auto pkt = pktfty->make_packet<PT_TURN>(plr_self, PLR_BROADCAST, turn);
|
|
|
|
|
send(*pkt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::SendFirstTurnIfReady(plr_t player)
|
|
|
|
|
{
|
|
|
|
|
if (awaitingSequenceNumber_)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
PlayerState &playerState = playerStateTable_[plr_self];
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
if (turnQueue.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
turn_t turn = turnQueue.back();
|
|
|
|
|
auto pkt = pktfty->make_packet<PT_TURN>(plr_self, player, turn);
|
|
|
|
|
send(*pkt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::MakeReady(seq_t sequenceNumber)
|
|
|
|
|
{
|
|
|
|
|
if (!awaitingSequenceNumber_)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
next_turn = sequenceNumber;
|
|
|
|
|
awaitingSequenceNumber_ = false;
|
|
|
|
|
|
|
|
|
|
PlayerState &playerState = playerStateTable_[plr_self];
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
if (!turnQueue.empty()) {
|
|
|
|
|
turn_t &turn = turnQueue.front();
|
|
|
|
|
turn.SequenceNumber = next_turn;
|
|
|
|
|
SendTurnIfReady(turn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void base::SNetGetProviderCaps(struct _SNETCAPS *caps)
|
|
|
|
|
{
|
|
|
|
|
caps->size = 0; // engine writes only ?!?
|
|
|
|
|
caps->flags = 0; // unused
|
|
|
|
|
caps->maxmessagesize = 512; // capped to 512; underflow if < 24
|
|
|
|
|
caps->maxqueuesize = 0; // unused
|
|
|
|
|
caps->maxplayers = MAX_PLRS; // capped to 4
|
|
|
|
|
caps->bytessec = 1000000; // ?
|
|
|
|
|
caps->latencyms = 0; // unused
|
|
|
|
|
caps->defaultturnssec = 10; // ?
|
|
|
|
|
caps->defaultturnsintransit = 1; // maximum acceptable number
|
|
|
|
|
// of turns in queue?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetUnregisterEventHandler(event_type evtype)
|
|
|
|
|
{
|
|
|
|
|
registered_handlers.erase(evtype);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
engine registers handler for:
|
|
|
|
|
EVENT_TYPE_PLAYER_LEAVE_GAME
|
|
|
|
|
EVENT_TYPE_PLAYER_CREATE_GAME (should be raised during SNetCreateGame
|
|
|
|
|
for non-creating player)
|
|
|
|
|
EVENT_TYPE_PLAYER_MESSAGE (for bnet? not implemented)
|
|
|
|
|
(engine uses same function for all three)
|
|
|
|
|
*/
|
|
|
|
|
registered_handlers[evtype] = func;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetLeaveGame(int type)
|
|
|
|
|
{
|
|
|
|
|
auto pkt = pktfty->make_packet<PT_DISCONNECT>(plr_self, PLR_BROADCAST,
|
|
|
|
|
plr_self, type);
|
|
|
|
|
send(*pkt);
|
|
|
|
|
plr_self = PLR_BROADCAST;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetDropPlayer(int playerid, uint32_t flags)
|
|
|
|
|
{
|
|
|
|
|
auto pkt = pktfty->make_packet<PT_DISCONNECT>(plr_self,
|
|
|
|
|
PLR_BROADCAST,
|
|
|
|
|
(plr_t)playerid,
|
|
|
|
|
(leaveinfo_t)flags);
|
|
|
|
|
send(*pkt);
|
|
|
|
|
RecvLocal(*pkt);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plr_t base::GetOwner()
|
|
|
|
|
{
|
|
|
|
|
for (auto i = 0; i < MAX_PLRS; ++i) {
|
|
|
|
|
if (IsConnected(i)) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return PLR_BROADCAST; // should be unreachable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetGetOwnerTurnsWaiting(uint32_t *turns)
|
|
|
|
|
{
|
|
|
|
|
poll();
|
|
|
|
|
|
|
|
|
|
plr_t owner = GetOwner();
|
|
|
|
|
PlayerState &playerState = playerStateTable_[owner];
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
*turns = turnQueue.size();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool base::SNetGetTurnsInTransit(uint32_t *turns)
|
|
|
|
|
{
|
|
|
|
|
PlayerState &playerState = playerStateTable_[plr_self];
|
|
|
|
|
std::deque<turn_t> &turnQueue = playerState.turnQueue;
|
|
|
|
|
*turns = turnQueue.size();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace net
|
|
|
|
|
} // namespace devilution
|