1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-13 00:24:24 +02:00

#142 network multiplayer basic protocol and routine (WIP)

This commit is contained in:
XProger
2018-11-26 11:00:34 +03:00
parent d28d38127c
commit 91ecfe0adb
10 changed files with 540 additions and 10 deletions

View File

@@ -15,6 +15,7 @@
#define _GAPI_GL 1
//#define _GAPI_D3D9 1
//#define _GAPI_VULKAN 1
//#define _NAPI_SOCKET
#include <windows.h>
@@ -324,6 +325,12 @@ namespace Core {
#include "input.h"
#include "sound.h"
#if defined(_NAPI_SOCKET)
#include "napi_socket.h"
#else
#include "napi_dummy.h"
#endif
#define MAX_LIGHTS 4
#define MAX_RENDER_BUFFERS 32
#define MAX_CONTACTS 15
@@ -622,6 +629,7 @@ namespace Core {
Input::init();
Sound::init();
NAPI::init();
GAPI::init();
@@ -770,7 +778,7 @@ namespace Core {
delete ditherTex;
GAPI::deinit();
NAPI::deinit();
Sound::deinit();
}

View File

@@ -173,6 +173,7 @@ namespace Game {
void updateTick() {
Input::update();
Network::update();
cheatControl(Input::lastState[0]);

View File

@@ -320,6 +320,8 @@ struct Lara : Character {
float hitTimer;
int32 networkInput;
#ifdef _DEBUG
//uint16 *dbgBoxes;
//int dbgBoxesCount;
@@ -494,8 +496,9 @@ struct Lara : Character {
Lara(IGame *game, int entity) : Character(game, entity, LARA_MAX_HEALTH), dozy(false), wpnCurrent(TR::Entity::NONE), wpnNext(TR::Entity::NONE), braid(NULL) {
camera = new Camera(game, this);
itemHolster = TR::Entity::NONE;
hitTimer = 0.0f;
itemHolster = TR::Entity::NONE;
hitTimer = 0.0f;
networkInput = -1;
if (level->extra.laraSkin > -1)
level->entities[entity].modelIndex = level->extra.laraSkin + 1;
@@ -2893,6 +2896,10 @@ struct Lara : Character {
virtual int getInput() { // TODO: updateInput
if (level->isCutsceneLevel()) return 0;
if (networkInput != -1)
return networkInput;
input = 0;
int pid = camera->cameraIndex;

View File

@@ -11,6 +11,7 @@
#include "trigger.h"
#include "inventory.h"
#include "savegame.h"
#include "network.h"
#if defined(_DEBUG) && defined(_GAPI_GL) && !defined(_GAPI_GLES)
#define DEBUG_RENDER
@@ -872,10 +873,14 @@ struct Level : IGame {
loadSlot = -1;
}
Network::start(this);
Core::resetTime();
}
virtual ~Level() {
Network::stop();
for (int i = 0; i < level.entitiesCount; i++)
delete (Controller*)level.entities[i].controller;
@@ -1812,6 +1817,13 @@ struct Level : IGame {
sndWater->setVolume(volWater, 0.2f);
if (sndTrack && sndTrack->volumeTarget != volTrack)
sndTrack->setVolume(volTrack, 0.2f);
#ifdef _DEBUG
if (Input::down[ikJ]) {
Network::sayHello();
Input::down[ikJ] = false;
}
#endif
}
void updateEffect() {

17
src/napi_dummy.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef H_NAPI_DUMMY
#define H_NAPI_DUMMYT
#include "utils.h"
namespace NAPI {
typedef int Peer;
void init() {}
void deinit() {}
void listen(uint16 port) {}
int send(const Peer &to, const void *data, int size) { return 0; }
int recv(Peer &from, void *data, int size) { return 0; }
void broadcast(const void *data, int size) {}
}
#endif

103
src/napi_socket.h Normal file
View File

@@ -0,0 +1,103 @@
#ifndef H_NAPI_SOCKET
#define H_NAPI_SOCKET
#include "utils.h"
#ifdef _OS_WIN
#include "winsock.h"
#endif
namespace NAPI {
struct Peer {
uint16 port;
uint32 ip;
inline bool operator == (const Peer &peer) const {
return port == peer.port && ip == peer.ip;
}
};
SOCKET sock;
sockaddr_in addr;
uint16 port;
void init() {
sock = INVALID_SOCKET;
WSAData wData;
WSAStartup(0x0101, &wData);
}
void deinit() {
if (sock != INVALID_SOCKET) {
shutdown(sock, 1);
#ifdef _OS_WIN
closesocket(sock);
#else
close(sock);
#endif
}
WSACleanup();
}
void listen(uint16 port) {
NAPI::port = port;
if (sock != INVALID_SOCKET) return;
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
LOG("! network: failed to create socket\n");
sock = INVALID_SOCKET;
return;
}
u_long on = 1;
if (ioctlsocket(sock, FIONBIO, &on) < 0) {
LOG("! network: failed to set non-blocking mode\n");
closesocket(sock);
sock = INVALID_SOCKET;
return;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (bind(sock, (sockaddr*)&addr, sizeof(addr)))
LOG("! network: unable to bind socket on port (%d)\n", (int)port);
on = 1;
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(on)))
LOG("! network: unable to enable broadcasting\n");
}
int send(const Peer &to, const void *data, int size) {
if (sock == INVALID_SOCKET) return false;
addr.sin_addr.s_addr = to.ip;
addr.sin_port = to.port;
return sendto(sock, (const char*)data, size, 0, (sockaddr*)&addr, sizeof(addr));
}
int recv(Peer &from, void *data, int size) {
if (sock == INVALID_SOCKET) return false;
int i = sizeof(addr);
int count = recvfrom(sock, (char*)data, size, 0, (sockaddr*)&addr, &i);
if (count > 0) {
from.ip = addr.sin_addr.s_addr;
from.port = addr.sin_port;
}
return count;
}
void broadcast(const void *data, int size) {
Peer peer;
peer.ip = INADDR_BROADCAST;
peer.port = htons(port);
send(peer, data, size);
}
}
#endif

354
src/network.h Normal file
View File

@@ -0,0 +1,354 @@
#ifndef H_NET
#define H_NET
#include "core.h"
#include "utils.h"
#include "format.h"
#include "controller.h"
#include "ui.h"
#define NET_PROTOCOL 1
#define NET_PORT 21468
#define NET_PING_TIMEOUT ( 1000 * 10 )
#define NET_PING_PERIOD ( 1000 * 3 )
#define NET_SYMC_INPUT_PERIOD ( 1000 / 25 )
#define NET_SYMC_STATE_PERIOD ( 1000 / 1000 )
namespace Network {
struct Packet {
enum Type {
HELLO, INFO, PING, PONG, JOIN, ACCEPT, REJECT, INPUT, STATE,
};
uint16 type;
uint16 id;
union {
struct {
uint8 protocol;
uint8 game;
} hello;
struct {
str16 name;
uint8 level;
uint8 players;
struct {
uint16 secure:1;
} flags;
} info;
struct {
str16 nick;
str16 pass;
} join;
struct {
uint16 id;
uint8 level;
uint8 roomIndex;
int16 posX;
int16 posY;
int16 posZ;
int16 angle;
} accept;
struct {
uint16 reason;
} reject;
struct {
uint16 mask;
} input;
struct {
uint8 roomIndex;
uint8 reserved;
int16 pos[3];
int16 angle[2];
uint8 frame;
uint8 stand;
uint16 animIndex;
} state;
};
int getSize() const {
const int sizes[] = {
sizeof(hello),
sizeof(info),
0,
0,
sizeof(join),
sizeof(accept),
sizeof(reject),
sizeof(input),
sizeof(state),
};
if (type >= 0 && type < COUNT(sizes))
return 2 + 2 + sizes[type];
ASSERT(false);
return 0;
}
};
IGame *game;
struct Player {
NAPI::Peer peer;
int pingTime;
int pingIndex;
Controller *controller;
};
Array<Player> players;
int syncInputTime;
int syncStateTime;
void start(IGame *game) {
Network::game = game;
NAPI::listen(NET_PORT);
syncInputTime = syncStateTime = osGetTime();
}
void stop() {
players.clear();
}
bool sendPacket(const NAPI::Peer &to, const Packet &packet) {
return NAPI::send(to, &packet, packet.getSize()) > 0;
}
bool recvPacket(NAPI::Peer &from, Packet &packet) {
int count = NAPI::recv(from, &packet, sizeof(packet));
if (count > 0) {
if (count != packet.getSize()) {
ASSERT(false);
return false;
}
return true;
}
return false;
}
void sayHello() {
Packet packet;
packet.type = Packet::HELLO;
packet.hello.protocol = NET_PROTOCOL;
packet.hello.game = game->getLevel()->version & TR::VER_VERSION;
NAPI::broadcast(&packet, packet.getSize());
}
void joinGame(const NAPI::Peer &peer) {
Packet packet;
packet.type = Packet::JOIN;
packet.join.nick = "Player_2";
packet.join.pass = "";
LOG("join game\n");
sendPacket(peer, packet);
}
void pingPlayers(int time) {
int i = 0;
while (i < players.length) {
int delta = time - players[i].pingTime;
if (delta > NET_PING_TIMEOUT) {
players.removeFast(i);
continue;
}
if (delta > NET_PING_PERIOD) {
Packet packet;
packet.type = Packet::PING;
sendPacket(players[i].peer, packet);
}
i++;
}
}
void syncInput(int time) {
Lara *lara = (Lara*)game->getLara();
if (!lara) return;
if ((time - syncInputTime) < NET_SYMC_INPUT_PERIOD)
return;
Packet packet;
packet.type = Packet::INPUT;
packet.input.mask = lara->getInput();
for (int i = 0; i < players.length; i++)
sendPacket(players[i].peer, packet);
syncInputTime = time;
}
void syncState(int time) {
if ((time - syncStateTime) < NET_SYMC_STATE_PERIOD)
return;
// TODO
syncStateTime = time;
}
Player* getPlayerByPeer(const NAPI::Peer &peer) {
for (int i = 0; i < players.length; i++)
if (players[i].peer == peer) {
return &players[i];
}
return NULL;
}
void getSpawnPoint(uint8 &roomIndex, vec3 &pos, float &angle) {
Controller *lara = game->getLara();
roomIndex = lara->getRoomIndex();
pos = lara->getPos();
angle = normalizeAngle(lara->angle.y); // 0..2PI
}
void update() {
int count;
NAPI::Peer from;
Packet packet, response;
int time = osGetTime();
while ( (count = recvPacket(from, packet)) > 0 ) {
Player *player = getPlayerByPeer(from);
if (player)
player->pingTime = time;
switch (packet.type) {
case Packet::HELLO :
if (game->getLevel()->isTitle())
break;
LOG("recv HELLO\n");
if (packet.hello.game != (game->getLevel()->version & TR::VER_VERSION))
break;
if (packet.hello.protocol != NET_PROTOCOL)
break;
LOG("send INFO\n");
response.type = Packet::INFO;
response.info.name = "MultiOpenLara";
response.info.level = game->getLevel()->id;
response.info.players = players.length + 1;
response.info.flags.secure = false;
sendPacket(from, response);
break;
case Packet::INFO : {
LOG("recv INFO\n");
char buf[sizeof(packet.info.name) + 1];
packet.info.name.get(buf);
LOG("name: %s\n", buf);
joinGame(from);
break;
}
case Packet::PING :
if (player) {
response.type = Packet::PONG;
sendPacket(from, response);
}
break;
case Packet::PONG :
break;
case Packet::JOIN :
if (!player) {
uint8 roomIndex;
vec3 pos;
float angle;
getSpawnPoint(roomIndex, pos, angle);
Player newPlayer;
newPlayer.peer = from;
newPlayer.pingIndex = 0;
newPlayer.pingTime = time;
newPlayer.controller = game->addEntity(TR::Entity::LARA, roomIndex, pos, angle);
players.push(newPlayer);
((Lara*)newPlayer.controller)->networkInput = 0;
char buf[32];
packet.join.nick.get(buf);
LOG("Player %s joined\n", buf);
ASSERT(newPlayer.controller);
TR::Room &room = game->getLevel()->rooms[roomIndex];
vec3 offset = pos - room.getOffset();
response.type = Packet::ACCEPT;
response.accept.id = 0;
response.accept.level = game->getLevel()->id;
response.accept.roomIndex = roomIndex;
response.accept.posX = int16(offset.x);
response.accept.posY = int16(offset.y);
response.accept.posZ = int16(offset.z);
response.accept.angle = int16(angle * RAD2DEG);
sendPacket(from, response);
}
break;
case Packet::ACCEPT : {
LOG("accept!\n");
game->loadLevel(TR::LevelID(packet.accept.level));
inventory->toggle();
break;
}
case Packet::REJECT :
break;
case Packet::INPUT :
if (game->getLevel()->isTitle())
break;
if (!player) {
uint8 roomIndex;
vec3 pos;
float angle;
getSpawnPoint(roomIndex, pos, angle);
Player newPlayer;
newPlayer.peer = from;
newPlayer.pingIndex = 0;
newPlayer.pingTime = time;
newPlayer.controller = game->addEntity(TR::Entity::LARA, roomIndex, pos, angle);
players.push(newPlayer);
((Lara*)newPlayer.controller)->networkInput = 0;
player = getPlayerByPeer(from);
}
if (player) {
((Lara*)player->controller)->networkInput = packet.input.mask;
}
break;
case Packet::STATE :
break;
}
}
pingPlayers(time);
syncInput(time);
syncState(time);
}
}
#endif

View File

@@ -106,7 +106,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>openvr_api.lib;d3d9.lib;opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wsock32.lib;openvr_api.lib;d3d9.lib;opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Editor|Win32'">
@@ -149,7 +149,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>wcrt.lib;openvr_api.lib;d3d9.lib;opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wcrt.lib;wsock32.lib;openvr_api.lib;d3d9.lib;opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
@@ -179,7 +179,7 @@
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>wcrt.lib;openvr_api.lib;opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wcrt.lib;wsock32.lib;openvr_api.lib;opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
@@ -199,6 +199,7 @@
<ClInclude Include="..\..\collision.h" />
<ClInclude Include="..\..\controller.h" />
<ClInclude Include="..\..\core.h" />
<ClInclude Include="..\..\format.h" />
<ClInclude Include="..\..\gapi_d3d9.h" />
<ClInclude Include="..\..\gapi_gl.h" />
<ClInclude Include="..\..\gapi_gu.h" />
@@ -209,12 +210,14 @@
<ClInclude Include="..\..\frustum.h" />
<ClInclude Include="..\..\game.h" />
<ClInclude Include="..\..\gameflow.h" />
<ClInclude Include="..\..\libs\tinf\tinf.h" />
<ClInclude Include="..\..\ui.h" />
<ClInclude Include="..\..\napi_dummy.h" />
<ClInclude Include="..\..\napi_socket.h" />
<ClInclude Include="..\..\network.h" />
<ClInclude Include="..\..\input.h" />
<ClInclude Include="..\..\inventory.h" />
<ClInclude Include="..\..\lara.h" />
<ClInclude Include="..\..\level.h" />
<ClInclude Include="..\..\libs\tinf\tinf.h" />
<ClInclude Include="..\..\libs\minimp3\libc.h" />
<ClInclude Include="..\..\libs\minimp3\minimp3.h" />
<ClInclude Include="..\..\mesh.h" />
@@ -223,9 +226,9 @@
<ClInclude Include="..\..\sound.h" />
<ClInclude Include="..\..\sprite.h" />
<ClInclude Include="..\..\texture.h" />
<ClInclude Include="..\..\format.h" />
<ClInclude Include="..\..\trigger.h" />
<ClInclude Include="..\..\utils.h" />
<ClInclude Include="..\..\ui.h" />
<ClInclude Include="..\..\video.h" />
</ItemGroup>
<ItemGroup>

View File

@@ -54,6 +54,9 @@
</ClInclude>
<ClInclude Include="..\..\video.h" />
<ClInclude Include="..\..\savegame.h" />
<ClInclude Include="..\..\network.h" />
<ClInclude Include="..\..\napi_socket.h" />
<ClInclude Include="..\..\napi_dummy.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\shaders\filter.glsl">

View File

@@ -1638,6 +1638,27 @@ namespace String {
}
template <int N>
struct FixedStr {
char data[N];
void get(char *dst) {
memcpy(dst, data, sizeof(data));
dst[sizeof(data)] = 0;
}
FixedStr<N>& operator = (const char *str) {
int len = min(sizeof(data), strlen(str));
memset(data, 0, sizeof(data));
memcpy(data, str, len);
return *this;
}
};
typedef FixedStr<16> str16;
template <typename T>
struct Array {
int capacity;
@@ -1675,7 +1696,8 @@ struct Array {
}
void removeFast(int index) {
(*this)[index] = (*this)[--length];
(*this)[index] = (*this)[length - 1];
length--;
}
void remove(int index) {