refactor: Refactor multiplayer, optimize message exchange
This commit is contained in:
parent
2e73eca295
commit
64b8ad60fd
23 changed files with 578 additions and 282 deletions
|
@ -68,6 +68,7 @@ set(HEADERS
|
|||
src/game/multiplayer/door.h
|
||||
src/game/multiplayer/game.h
|
||||
src/game/multiplayer/module.h
|
||||
src/game/multiplayer/util.h
|
||||
src/game/object/creature.h
|
||||
src/game/object/door.h
|
||||
src/game/object/object.h
|
||||
|
@ -91,6 +92,7 @@ set(HEADERS
|
|||
src/gui/types.h
|
||||
src/net/client.h
|
||||
src/net/connection.h
|
||||
src/net/command.h
|
||||
src/net/server.h
|
||||
src/net/types.h
|
||||
src/program.h
|
||||
|
@ -170,6 +172,7 @@ set(SOURCES
|
|||
src/game/multiplayer/door.cpp
|
||||
src/game/multiplayer/game.cpp
|
||||
src/game/multiplayer/module.cpp
|
||||
src/game/multiplayer/util.cpp
|
||||
src/game/navmesh.cpp
|
||||
src/game/object/creature.cpp
|
||||
src/game/object/door.cpp
|
||||
|
@ -195,6 +198,7 @@ set(SOURCES
|
|||
src/main.cpp
|
||||
src/net/client.cpp
|
||||
src/net/connection.cpp
|
||||
src/net/command.cpp
|
||||
src/net/server.cpp
|
||||
src/program.cpp
|
||||
src/render/aabb.cpp
|
||||
|
|
|
@ -85,11 +85,13 @@ void Area::load(const GffStruct &are, const GffStruct &git) {
|
|||
shared_ptr<Creature> creature(makeCreature());
|
||||
creature->load(gffs);
|
||||
landObject(*creature);
|
||||
creature->setSynchronize(true);
|
||||
_objects[ObjectType::Creature].push_back(move(creature));
|
||||
}
|
||||
for (auto &gffs : git.getList("Door List")) {
|
||||
shared_ptr<Door> door(makeDoor());
|
||||
door->load(gffs);
|
||||
door->setSynchronize(true);
|
||||
_objects[ObjectType::Door].push_back(move(door));
|
||||
}
|
||||
for (auto &gffs : git.getList("Placeable List")) {
|
||||
|
@ -118,8 +120,8 @@ void Area::load(const GffStruct &are, const GffStruct &git) {
|
|||
});
|
||||
}
|
||||
|
||||
shared_ptr<Creature> Area::makeCreature() {
|
||||
return make_unique<Creature>(_idCounter++);
|
||||
shared_ptr<Creature> Area::makeCreature(uint32_t id) {
|
||||
return make_unique<Creature>(id > 0 ? id : _idCounter++);
|
||||
}
|
||||
|
||||
shared_ptr<Door> Area::makeDoor() {
|
||||
|
@ -204,15 +206,19 @@ void Area::landObject(Object &object) {
|
|||
}
|
||||
|
||||
void Area::loadParty(const PartyConfiguration &party, const glm::vec3 &position, float heading) {
|
||||
shared_ptr<Creature> partyLeader(makeCharacter(party.leader, kPartyLeaderTag, position, heading));
|
||||
_objects[ObjectType::Creature].push_back(partyLeader);
|
||||
landObject(*partyLeader);
|
||||
_player = partyLeader;
|
||||
_partyLeader = partyLeader;
|
||||
|
||||
if (party.memberCount > 0) {
|
||||
shared_ptr<Creature> partyLeader(makeCharacter(party.leader, kPartyLeaderTag, position, heading));
|
||||
_objects[ObjectType::Creature].push_back(partyLeader);
|
||||
landObject(*partyLeader);
|
||||
partyLeader->setSynchronize(true);
|
||||
_player = partyLeader;
|
||||
_partyLeader = partyLeader;
|
||||
}
|
||||
|
||||
if (party.memberCount > 1) {
|
||||
shared_ptr<Creature> partyMember(makeCharacter(party.member1, kPartyMember1Tag, position, heading));
|
||||
landObject(*partyMember);
|
||||
partyMember->setSynchronize(true);
|
||||
_objects[ObjectType::Creature].push_back(partyMember);
|
||||
_partyMember1 = partyMember;
|
||||
|
||||
|
@ -220,9 +226,10 @@ void Area::loadParty(const PartyConfiguration &party, const glm::vec3 &position,
|
|||
partyMember->enqueueAction(move(action));
|
||||
}
|
||||
|
||||
if (party.memberCount > 1) {
|
||||
if (party.memberCount > 2) {
|
||||
shared_ptr<Creature> partyMember(makeCharacter(party.member2, kPartyMember2Tag, position, heading));
|
||||
landObject(*partyMember);
|
||||
partyMember->setSynchronize(true);
|
||||
_objects[ObjectType::Creature].push_back(partyMember);
|
||||
_partyMember2 = partyMember;
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ protected:
|
|||
|
||||
void landObject(Object &object);
|
||||
|
||||
virtual std::shared_ptr<Creature> makeCreature();
|
||||
virtual std::shared_ptr<Creature> makeCreature(uint32_t id = 0);
|
||||
virtual std::shared_ptr<Door> makeDoor();
|
||||
virtual std::shared_ptr<Placeable> makePlaceable();
|
||||
virtual std::shared_ptr<Waypoint> makeWaypoint();
|
||||
|
|
|
@ -124,7 +124,7 @@ void Game::loadMainMenu() {
|
|||
mainMenu->setOnExit([this]() { _quit = true; });
|
||||
mainMenu->setOnModuleSelected([this](const string &name) {
|
||||
PartyConfiguration party;
|
||||
party.memberCount = 1;
|
||||
party.memberCount = 2;
|
||||
party.leader.equipment.push_back("g_a_clothes01");
|
||||
party.member1.equipment.push_back("g_a_clothes01");
|
||||
|
||||
|
@ -170,6 +170,7 @@ void Game::loadPortraitsGui() {
|
|||
string moduleName(_version == GameVersion::KotOR ? "end_m01aa" : "001ebo");
|
||||
|
||||
PartyConfiguration party;
|
||||
party.memberCount = 1;
|
||||
party.leader = character;
|
||||
|
||||
loadModule(moduleName, party);
|
||||
|
|
|
@ -168,12 +168,16 @@ void Module::getEntryPoint(const string &waypoint, glm::vec3 &position, float &h
|
|||
}
|
||||
|
||||
void Module::update3rdPersonCameraTarget() {
|
||||
Object *player = _area->player().get();
|
||||
shared_ptr<Object> player(_area->player());
|
||||
if (!player) return;
|
||||
|
||||
_thirdPersonCamera->setTargetPosition(player->position() + player->model()->getNodeAbsolutePosition("camerahook"));
|
||||
}
|
||||
|
||||
void Module::update3rdPersonCameraHeading() {
|
||||
Object *player = _area->player().get();
|
||||
shared_ptr<Object> player(_area->player());
|
||||
if (!player) return;
|
||||
|
||||
_thirdPersonCamera->setHeading(player->heading());
|
||||
}
|
||||
|
||||
|
@ -364,7 +368,10 @@ void Module::updatePlayer(float dt) {
|
|||
if (_cameraType != CameraType::ThirdPerson) return;
|
||||
|
||||
ThirdPersonCamera &camera = static_cast<ThirdPersonCamera &>(*_thirdPersonCamera);
|
||||
Creature *player = static_cast<Creature *>(_area->player().get());
|
||||
shared_ptr<Object> playerObject(_area->player());
|
||||
if (!playerObject) return;
|
||||
|
||||
Creature &player = static_cast<Creature &>(*playerObject);
|
||||
|
||||
float heading = 0.0f;
|
||||
bool movement = false;
|
||||
|
@ -378,16 +385,16 @@ void Module::updatePlayer(float dt) {
|
|||
}
|
||||
|
||||
if (movement) {
|
||||
glm::vec3 target(player->position());
|
||||
glm::vec3 target(player.position());
|
||||
target.x -= 100.0f * glm::sin(heading);
|
||||
target.y += 100.0f * glm::cos(heading);
|
||||
|
||||
if (_area->moveCreatureTowards(*player, target, dt)) {
|
||||
player->setMovementType(MovementType::Run);
|
||||
if (_area->moveCreatureTowards(player, target, dt)) {
|
||||
player.setMovementType(MovementType::Run);
|
||||
update3rdPersonCameraTarget();
|
||||
}
|
||||
} else {
|
||||
player->setMovementType(MovementType::None);
|
||||
player.setMovementType(MovementType::None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::net;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace game {
|
||||
|
@ -37,8 +39,8 @@ MultiplayerArea::MultiplayerArea(
|
|||
_scriptsEnabled = mode == MultiplayerMode::Server;
|
||||
}
|
||||
|
||||
shared_ptr<Creature> MultiplayerArea::makeCreature() {
|
||||
return make_unique<MultiplayerCreature>(_idCounter++, _callbacks);
|
||||
shared_ptr<Creature> MultiplayerArea::makeCreature(uint32_t id) {
|
||||
return make_unique<MultiplayerCreature>(id > 0 ? id : _idCounter++, _callbacks);
|
||||
}
|
||||
|
||||
shared_ptr<Door> MultiplayerArea::makeDoor() {
|
||||
|
@ -76,7 +78,7 @@ void MultiplayerArea::execute(const Command &cmd) {
|
|||
}
|
||||
}
|
||||
void MultiplayerArea::executeLoadCreature(const Command &cmd) {
|
||||
shared_ptr<Creature> creature(makeCreature());
|
||||
shared_ptr<Creature> creature(makeCreature(cmd.objectId()));
|
||||
creature->setTag(cmd.tag());
|
||||
|
||||
for (auto &item : cmd.equipment()) {
|
||||
|
@ -89,6 +91,7 @@ void MultiplayerArea::executeLoadCreature(const Command &cmd) {
|
|||
creature->load(move(config));
|
||||
creature->setPosition(cmd.position());
|
||||
creature->setHeading(cmd.heading());
|
||||
creature->setSynchronize(true);
|
||||
creature->initGL();
|
||||
|
||||
switch (cmd.role()) {
|
||||
|
@ -130,7 +133,7 @@ void MultiplayerArea::executeSetPlayerRole(const Command &cmd) {
|
|||
}
|
||||
|
||||
void MultiplayerArea::executeSetObjectTransform(const Command &cmd) {
|
||||
shared_ptr<Object> object(find(cmd.tag()));
|
||||
shared_ptr<Object> object(find(cmd.objectId(), ObjectType::Creature));
|
||||
if (object) {
|
||||
object->setSynchronize(false);
|
||||
object->setPosition(cmd.position());
|
||||
|
@ -140,7 +143,7 @@ void MultiplayerArea::executeSetObjectTransform(const Command &cmd) {
|
|||
}
|
||||
|
||||
void MultiplayerArea::executeSetObjectAnimation(const Command &cmd) {
|
||||
shared_ptr<Object> object(find(cmd.tag()));
|
||||
shared_ptr<Object> object(find(cmd.objectId(), ObjectType::Creature));
|
||||
if (object) {
|
||||
object->setSynchronize(false);
|
||||
object->animate(cmd.animation(), cmd.animationFlags());
|
||||
|
@ -149,7 +152,7 @@ void MultiplayerArea::executeSetObjectAnimation(const Command &cmd) {
|
|||
}
|
||||
|
||||
void MultiplayerArea::executeSetCreatureMovementType(const Command &cmd) {
|
||||
shared_ptr<Object> creature(find(cmd.tag(), ObjectType::Creature));
|
||||
shared_ptr<Object> creature(find(cmd.objectId(), ObjectType::Creature));
|
||||
if (creature) {
|
||||
creature->setSynchronize(false);
|
||||
static_cast<Creature &>(*creature).setMovementType(cmd.movementType());
|
||||
|
@ -159,7 +162,7 @@ void MultiplayerArea::executeSetCreatureMovementType(const Command &cmd) {
|
|||
|
||||
void MultiplayerArea::executeSetDoorOpen(const Command &cmd) {
|
||||
shared_ptr<Object> door(find(cmd.objectId(), ObjectType::Door));
|
||||
shared_ptr<Object> trigerrer(find(cmd.trigerrer()));
|
||||
shared_ptr<Object> trigerrer(find(cmd.triggerrer()));
|
||||
if (door) {
|
||||
door->setSynchronize(false);
|
||||
static_cast<Door &>(*door).open(trigerrer);
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
private:
|
||||
IMultiplayerCallbacks *_callbacks { nullptr };
|
||||
|
||||
std::shared_ptr<Creature> makeCreature() override;
|
||||
std::shared_ptr<Creature> makeCreature(uint32_t id) override;
|
||||
std::shared_ptr<Door> makeDoor() override;
|
||||
void updateCreature(Creature &creature, float dt) override;
|
||||
|
||||
|
|
|
@ -44,6 +44,13 @@ static void putUint16(uint16_t val, ByteArray &arr) {
|
|||
arr.push_back((val >> 8) & 0xff);
|
||||
}
|
||||
|
||||
static void putUint32(uint32_t val, ByteArray &arr) {
|
||||
arr.push_back(val & 0xff);
|
||||
arr.push_back((val >> 8) & 0xff);
|
||||
arr.push_back((val >> 16) & 0xff);
|
||||
arr.push_back((val >> 24) & 0xff);
|
||||
}
|
||||
|
||||
static void putFloat(float val, ByteArray &arr) {
|
||||
uint32_t intVal = *reinterpret_cast<uint32_t *>(&val);
|
||||
arr.push_back(intVal & 0xff);
|
||||
|
@ -69,28 +76,37 @@ static uint16_t getUint16(const ByteArray &arr, int &offset) {
|
|||
return val;
|
||||
}
|
||||
|
||||
static uint32_t getUint32(const ByteArray &arr, int &offset) {
|
||||
uint32_t val = *reinterpret_cast<const uint32_t *>(&arr[offset]);
|
||||
offset += sizeof(uint32_t);
|
||||
return val;
|
||||
}
|
||||
|
||||
static float getFloat(const ByteArray &arr, int &offset) {
|
||||
float val = *reinterpret_cast<const float *>(&arr[offset]);
|
||||
offset += sizeof(float);
|
||||
return val;
|
||||
}
|
||||
|
||||
Command::Command(CommandType type) : _type(type) {
|
||||
Command::Command(uint32_t id, net::CommandType type) : net::Command(id, type) {
|
||||
}
|
||||
|
||||
void Command::load(const ByteArray &data) {
|
||||
_type = static_cast<CommandType>(data[0]);
|
||||
_type = static_cast<net::CommandType>(data[0]);
|
||||
|
||||
int offset = 1;
|
||||
int equipmentCount = 0;
|
||||
|
||||
_id = getUint32(data, offset);
|
||||
|
||||
switch (_type) {
|
||||
case CommandType::LoadModule:
|
||||
case net::CommandType::LoadModule:
|
||||
_module = getString(data, offset);
|
||||
break;
|
||||
|
||||
case CommandType::LoadCreature:
|
||||
case net::CommandType::LoadCreature:
|
||||
_role = static_cast<CreatureRole>(getUint8(data, offset));
|
||||
_objectId = getUint16(data, offset);
|
||||
_tag = getString(data, offset);
|
||||
_appearance = getUint16(data, offset);
|
||||
_position.x = getFloat(data, offset);
|
||||
|
@ -103,51 +119,53 @@ void Command::load(const ByteArray &data) {
|
|||
}
|
||||
break;
|
||||
|
||||
case CommandType::SetPlayerRole:
|
||||
case net::CommandType::SetPlayerRole:
|
||||
_role = static_cast<CreatureRole>(getUint8(data, offset));
|
||||
break;
|
||||
|
||||
case CommandType::SetObjectTransform:
|
||||
_tag = getString(data, offset);
|
||||
case net::CommandType::SetObjectTransform:
|
||||
_objectId = getUint16(data, offset);
|
||||
_position.x = getFloat(data, offset);
|
||||
_position.y = getFloat(data, offset);
|
||||
_position.z = getFloat(data, offset);
|
||||
_heading = getFloat(data, offset);
|
||||
break;
|
||||
|
||||
case CommandType::SetObjectAnimation:
|
||||
_tag = getString(data, offset);
|
||||
case net::CommandType::SetObjectAnimation:
|
||||
_objectId = getUint16(data, offset);
|
||||
_animationFlags = getUint8(data, offset);
|
||||
_animation = getString(data, offset);
|
||||
break;
|
||||
|
||||
case CommandType::SetCreatureMovementType:
|
||||
_tag = getString(data, offset);
|
||||
case net::CommandType::SetCreatureMovementType:
|
||||
_objectId = getUint16(data, offset);
|
||||
_movementType = static_cast<MovementType>(getUint8(data, offset));
|
||||
break;
|
||||
|
||||
case CommandType::SetDoorOpen:
|
||||
case net::CommandType::SetDoorOpen:
|
||||
_open = getUint8(data, offset);
|
||||
_objectId = getUint16(data, offset);
|
||||
_trigerrer = getString(data, offset);
|
||||
_triggerrer = getUint16(data, offset);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw runtime_error("Unsupported command type: " + to_string(static_cast<int>(_type)));
|
||||
throw runtime_error("Command: unsupported type: " + to_string(static_cast<int>(_type)));
|
||||
}
|
||||
}
|
||||
|
||||
ByteArray Command::bytes() const {
|
||||
ByteArray data;
|
||||
putUint8(static_cast<uint8_t>(_type), data);
|
||||
putUint32(_id, data);
|
||||
|
||||
switch (_type) {
|
||||
case CommandType::LoadModule:
|
||||
case net::CommandType::LoadModule:
|
||||
putString(_module, data);
|
||||
break;
|
||||
|
||||
case CommandType::LoadCreature:
|
||||
case net::CommandType::LoadCreature:
|
||||
putUint8(static_cast<uint8_t>(_role), data);
|
||||
putUint16(_objectId, data);
|
||||
putString(_tag, data);
|
||||
putUint16(_appearance, data);
|
||||
putFloat(_position.x, data);
|
||||
|
@ -160,54 +178,46 @@ ByteArray Command::bytes() const {
|
|||
}
|
||||
break;
|
||||
|
||||
case CommandType::SetPlayerRole:
|
||||
case net::CommandType::SetPlayerRole:
|
||||
putUint8(static_cast<uint8_t>(_role), data);
|
||||
break;
|
||||
|
||||
case CommandType::SetObjectTransform:
|
||||
putString(_tag, data);
|
||||
case net::CommandType::SetObjectTransform:
|
||||
putUint16(_objectId, data);
|
||||
putFloat(_position.x, data);
|
||||
putFloat(_position.y, data);
|
||||
putFloat(_position.z, data);
|
||||
putFloat(_heading, data);
|
||||
break;
|
||||
|
||||
case CommandType::SetObjectAnimation:
|
||||
putString(_tag, data);
|
||||
case net::CommandType::SetObjectAnimation:
|
||||
putUint16(_objectId, data);
|
||||
putUint8(_animationFlags, data);
|
||||
putString(_animation, data);
|
||||
break;
|
||||
|
||||
case CommandType::SetCreatureMovementType:
|
||||
putString(_tag, data);
|
||||
case net::CommandType::SetCreatureMovementType:
|
||||
putUint16(_objectId, data);
|
||||
putUint8(static_cast<uint8_t>(_movementType), data);
|
||||
break;
|
||||
|
||||
case CommandType::SetDoorOpen:
|
||||
case net::CommandType::SetDoorOpen:
|
||||
putUint8(_open, data);
|
||||
putUint16(_objectId, data);
|
||||
putString(_trigerrer, data);
|
||||
putUint16(_triggerrer, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw runtime_error("Unsupported command type: " + to_string(static_cast<int>(_type)));
|
||||
throw runtime_error("Command: unsupported type: " + to_string(static_cast<int>(_type)));
|
||||
}
|
||||
|
||||
return move(data);
|
||||
}
|
||||
|
||||
CommandType Command::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
const string &Command::module() const {
|
||||
return _module;
|
||||
}
|
||||
|
||||
uint32_t Command::objectId() const {
|
||||
return _objectId;
|
||||
}
|
||||
|
||||
const string &Command::tag() const {
|
||||
return _tag;
|
||||
}
|
||||
|
@ -248,8 +258,8 @@ bool Command::open() const {
|
|||
return _open;
|
||||
}
|
||||
|
||||
const string &Command::trigerrer() const {
|
||||
return _trigerrer;
|
||||
uint32_t Command::triggerrer() const {
|
||||
return _triggerrer;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
|
|
|
@ -22,33 +22,23 @@
|
|||
|
||||
#include "../../core/types.h"
|
||||
#include "../../game/types.h"
|
||||
#include "../../net/command.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace game {
|
||||
|
||||
enum class CommandType {
|
||||
LoadModule,
|
||||
LoadCreature,
|
||||
SetPlayerRole,
|
||||
SetObjectTransform,
|
||||
SetObjectAnimation,
|
||||
SetCreatureMovementType,
|
||||
SetDoorOpen
|
||||
};
|
||||
|
||||
class Command {
|
||||
class Command : public net::Command {
|
||||
public:
|
||||
Command() = default;
|
||||
Command(CommandType type);
|
||||
Command(uint32_t id, net::CommandType type);
|
||||
|
||||
void load(const ByteArray &data);
|
||||
ByteArray bytes() const;
|
||||
|
||||
ByteArray bytes() const override;
|
||||
|
||||
// Getters
|
||||
CommandType type() const;
|
||||
const std::string &module() const;
|
||||
uint32_t objectId() const;
|
||||
const std::string &tag() const;
|
||||
CreatureRole role() const;
|
||||
int appearance() const;
|
||||
|
@ -59,12 +49,10 @@ public:
|
|||
int animationFlags() const;
|
||||
MovementType movementType() const;
|
||||
bool open() const;
|
||||
const std::string &trigerrer() const;
|
||||
uint32_t triggerrer() const;
|
||||
|
||||
private:
|
||||
CommandType _type { CommandType::LoadModule };
|
||||
std::string _module;
|
||||
uint32_t _objectId { 0 };
|
||||
std::string _tag;
|
||||
CreatureRole _role { CreatureRole::None };
|
||||
int _appearance { 0 };
|
||||
|
@ -76,7 +64,7 @@ private:
|
|||
float _animationSpeed { 1.0f };
|
||||
MovementType _movementType { MovementType::None };
|
||||
bool _open { false };
|
||||
std::string _trigerrer;
|
||||
uint32_t _triggerrer { 0 };
|
||||
|
||||
friend class MultiplayerGame;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "area.h"
|
||||
#include "module.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
@ -49,8 +50,9 @@ void MultiplayerGame::configure() {
|
|||
_server = make_unique<Server>();
|
||||
_server->setOnClientConnected(bind(&MultiplayerGame::onClientConnected, this, _1));
|
||||
_server->setOnClientDisconnected(bind(&MultiplayerGame::onClientDisconnected, this, _1));
|
||||
_server->setOnCommandReceived(bind(&MultiplayerGame::onCommandReceived, this, _1));
|
||||
_server->setOnCommandReceived(bind(&MultiplayerGame::onCommandReceived, this, _2));
|
||||
_server->start(_opts.network.port);
|
||||
Game::configure();
|
||||
break;
|
||||
|
||||
case MultiplayerMode::Client:
|
||||
|
@ -60,28 +62,135 @@ void MultiplayerGame::configure() {
|
|||
break;
|
||||
|
||||
default:
|
||||
Game::configure();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onClientConnected(const string tag) {
|
||||
assert(_module && _module->loaded());
|
||||
synchronizeClient(tag);
|
||||
}
|
||||
|
||||
void MultiplayerGame::synchronizeClient(const string &tag) {
|
||||
lock_guard<recursive_mutex> syncLock(_syncMutex);
|
||||
sendLoadModule(tag, _module->name());
|
||||
|
||||
shared_ptr<Object> partyLeader(_module->area().partyLeader());
|
||||
sendLoadCreature(tag, CreatureRole::PartyLeader, static_cast<Creature &>(*partyLeader));
|
||||
|
||||
shared_ptr<Object> partyMember1(_module->area().partyMember1());
|
||||
if (partyMember1) {
|
||||
Creature &creature = static_cast<Creature &>(*partyMember1);
|
||||
sendLoadCreature(tag, CreatureRole::PartyMember1, creature);
|
||||
}
|
||||
|
||||
shared_ptr<Object> partyMember2(_module->area().partyMember2());
|
||||
if (partyMember2) {
|
||||
Creature &creature = static_cast<Creature &>(*partyMember2);
|
||||
sendLoadCreature(tag, CreatureRole::PartyMember2, creature);
|
||||
}
|
||||
|
||||
bool control = false;
|
||||
if (partyMember1) {
|
||||
MultiplayerCreature &creature = static_cast<MultiplayerCreature &>(*partyMember1);
|
||||
if (!creature.isControlled()) {
|
||||
creature.setClientTag(tag);
|
||||
sendSetPlayerRole(tag, CreatureRole::PartyMember1);
|
||||
control = true;
|
||||
}
|
||||
}
|
||||
if (!control && partyMember2) {
|
||||
MultiplayerCreature &creature = static_cast<MultiplayerCreature &>(*partyMember2);
|
||||
if (!creature.isControlled()) {
|
||||
creature.setClientTag(tag);
|
||||
sendSetPlayerRole(tag, CreatureRole::PartyMember2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendLoadModule(const string &client, const string &module) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::LoadModule));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._module = module;
|
||||
|
||||
sendCommand(client, cmd);
|
||||
}
|
||||
|
||||
shared_ptr<net::Command> MultiplayerGame::makeCommand(net::CommandType type) {
|
||||
assert(_cmdCounter <= UINT_MAX);
|
||||
return shared_ptr<net::Command>(new Command(_cmdCounter++, type));
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendCommand(const string &client, const shared_ptr<net::Command> &command) {
|
||||
assert(_mode == MultiplayerMode::Server);
|
||||
_server->send(client, command);
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendLoadCreature(const string &client, CreatureRole role, const Creature &creature) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::LoadCreature));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._role = role;
|
||||
cmd2._objectId = creature.id();
|
||||
cmd2._tag = creature.tag();
|
||||
cmd2._appearance = creature.appearance();
|
||||
cmd2._position = creature.position();
|
||||
cmd2._heading = creature.heading();
|
||||
cmd2._equipment.clear();
|
||||
|
||||
for (auto &pair : creature.equipment()) {
|
||||
cmd2._equipment.push_back(pair.second->resRef());
|
||||
}
|
||||
|
||||
sendCommand(client, cmd);
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendSetPlayerRole(const string &client, CreatureRole role) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::SetPlayerRole));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._role = role;
|
||||
|
||||
sendCommand(client, cmd);
|
||||
}
|
||||
|
||||
void MultiplayerGame::onClientDisconnected(const string tag) {
|
||||
if (!_module || !_module->loaded()) return;
|
||||
|
||||
const MultiplayerArea &area = static_cast<MultiplayerArea &>(_module->area());
|
||||
shared_ptr<Object> object(area.findCreatureByClientTag(tag));
|
||||
if (object) {
|
||||
static_cast<MultiplayerCreature &>(*object).setClientTag("");
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onCommandReceived(const ByteArray &data) {
|
||||
Command cmd;
|
||||
cmd.load(data);
|
||||
|
||||
debug("Game: command received: " + describeCommand(cmd), 2);
|
||||
|
||||
lock_guard<recursive_mutex> lock(_commandsInMutex);
|
||||
_commandsIn.push(cmd);
|
||||
}
|
||||
|
||||
void MultiplayerGame::update() {
|
||||
lock_guard<recursive_mutex> lock(_commandsMutex);
|
||||
lock_guard<recursive_mutex> lock(_commandsInMutex);
|
||||
bool skip = false;
|
||||
|
||||
while (!_commands.empty() && !skip) {
|
||||
const Command &cmd = _commands.front();
|
||||
while (!_commandsIn.empty() && !skip) {
|
||||
const Command &cmd = _commandsIn.front();
|
||||
switch (cmd.type()) {
|
||||
case CommandType::LoadModule:
|
||||
if (!_module || _module->name() != cmd.module()) {
|
||||
_nextModule = cmd.module();
|
||||
}
|
||||
_commands.pop();
|
||||
_commandsIn.pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_module && _nextModule.empty()) {
|
||||
static_cast<MultiplayerArea &>(_module->area()).execute(cmd);
|
||||
_commands.pop();
|
||||
_commandsIn.pop();
|
||||
} else {
|
||||
skip = true;
|
||||
}
|
||||
|
@ -102,63 +211,13 @@ void MultiplayerGame::loadNextModule() {
|
|||
}
|
||||
}
|
||||
|
||||
const shared_ptr<Module> MultiplayerGame::makeModule(const string &name) {
|
||||
return shared_ptr<Module>(new MultiplayerModule(name, _mode, _version, _opts.graphics, this));
|
||||
}
|
||||
|
||||
void MultiplayerGame::onObjectTransformChanged(const Object &object, const glm::vec3 &position, float heading) {
|
||||
if (shouldSendObjectUpdates(object.tag())) {
|
||||
sendSetObjectTransformCommand(object.tag(), position, heading);
|
||||
if (shouldSendObjectUpdates(object.id())) {
|
||||
sendSetObjectTransform(object.id(), position, heading);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onObjectAnimationChanged(const Object &object, const string &anim, int flags, float speed) {
|
||||
if (shouldSendObjectUpdates(object.tag())) {
|
||||
sendSetObjectAnimationCommand(object.tag(), anim, flags, speed);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onCreatureMovementTypeChanged(const MultiplayerCreature &creature, MovementType type) {
|
||||
if (shouldSendObjectUpdates(creature.tag())) {
|
||||
sendSetCreatureMovementTypeCommand(creature.tag(), type);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onDoorOpen(const MultiplayerDoor &door, const shared_ptr<Object> &trigerrer) {
|
||||
string trigerrerTag(trigerrer ? trigerrer->tag() : "");
|
||||
if (shouldSendObjectUpdates(trigerrerTag)) {
|
||||
sendSetDoorOpenCommand(door.id(), trigerrerTag);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onClientConnected(const string tag) {
|
||||
if (!_module) return;
|
||||
|
||||
synchronizeClient(tag);
|
||||
}
|
||||
|
||||
void MultiplayerGame::onClientDisconnected(const string tag) {
|
||||
if (!_module) return;
|
||||
|
||||
const MultiplayerArea &area = static_cast<MultiplayerArea &>(_module->area());
|
||||
shared_ptr<Object> object(area.findCreatureByClientTag(tag));
|
||||
|
||||
if (object) {
|
||||
static_cast<MultiplayerCreature &>(*object).setClientTag("");
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::onCommandReceived(const ByteArray &data) {
|
||||
Command cmd;
|
||||
cmd.load(data);
|
||||
|
||||
debug("Command received: " + to_string(static_cast<int>(cmd.type())), 2);
|
||||
|
||||
lock_guard<recursive_mutex> lock(_commandsMutex);
|
||||
_commands.push(cmd);
|
||||
}
|
||||
|
||||
bool MultiplayerGame::shouldSendObjectUpdates(const string &tag) const {
|
||||
bool MultiplayerGame::shouldSendObjectUpdates(uint32_t objectId) const {
|
||||
if (!_module || !_module->loaded()) return false;
|
||||
|
||||
shared_ptr<Object> player(_module->area().player());
|
||||
|
@ -168,7 +227,7 @@ bool MultiplayerGame::shouldSendObjectUpdates(const string &tag) const {
|
|||
return true;
|
||||
|
||||
case MultiplayerMode::Client:
|
||||
return player && tag == player->tag();
|
||||
return player && objectId == player->id();
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -177,124 +236,78 @@ bool MultiplayerGame::shouldSendObjectUpdates(const string &tag) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void MultiplayerGame::synchronizeClient(const string &tag) {
|
||||
lock_guard<recursive_mutex> lock(_syncMutex);
|
||||
void MultiplayerGame::sendSetObjectTransform(uint32_t objectId, const glm::vec3 &position, float heading) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::SetObjectTransform));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._objectId = objectId;
|
||||
cmd2._position = position;
|
||||
cmd2._heading = heading;
|
||||
|
||||
sendLoadModuleCommand(tag, _module->name());
|
||||
sendCommand(cmd);
|
||||
}
|
||||
|
||||
shared_ptr<Object> partyLeader(_module->area().partyLeader());
|
||||
sendLoadCreatureCommand(tag, CreatureRole::PartyLeader, static_cast<Creature &>(*partyLeader));
|
||||
|
||||
shared_ptr<Object> partyMember1(_module->area().partyMember1());
|
||||
if (partyMember1) {
|
||||
Creature &creature = static_cast<Creature &>(*partyMember1);
|
||||
sendLoadCreatureCommand(tag, CreatureRole::PartyMember1, creature);
|
||||
}
|
||||
|
||||
shared_ptr<Object> partyMember2(_module->area().partyMember2());
|
||||
if (partyMember2) {
|
||||
Creature &creature = static_cast<Creature &>(*partyMember2);
|
||||
sendLoadCreatureCommand(tag, CreatureRole::PartyMember2, creature);
|
||||
}
|
||||
|
||||
bool control = false;
|
||||
if (partyMember1) {
|
||||
MultiplayerCreature &creature = static_cast<MultiplayerCreature &>(*partyMember1);
|
||||
if (!creature.isControlled()) {
|
||||
creature.setClientTag(tag);
|
||||
sendSetPlayerRoleCommand(tag, CreatureRole::PartyMember1);
|
||||
control = true;
|
||||
}
|
||||
}
|
||||
if (!control && partyMember2) {
|
||||
MultiplayerCreature &creature = static_cast<MultiplayerCreature &>(*partyMember2);
|
||||
if (!creature.isControlled()) {
|
||||
creature.setClientTag(tag);
|
||||
sendSetPlayerRoleCommand(tag, CreatureRole::PartyMember2);
|
||||
}
|
||||
void MultiplayerGame::sendCommand(const shared_ptr<net::Command> &command) {
|
||||
switch (_mode) {
|
||||
case MultiplayerMode::Server:
|
||||
_server->sendToAll(command);
|
||||
break;
|
||||
default:
|
||||
_client->send(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendLoadModuleCommand(const string &client, const string &module) {
|
||||
Command cmd(CommandType::LoadModule);
|
||||
cmd._module = module;
|
||||
|
||||
_server->send(client, cmd.bytes());
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendLoadCreatureCommand(const string &client, CreatureRole role, const Creature &creature) {
|
||||
Command cmd(CommandType::LoadCreature);
|
||||
cmd._role = role;
|
||||
cmd._tag = creature.tag();
|
||||
cmd._appearance = creature.appearance();
|
||||
cmd._position = creature.position();
|
||||
cmd._heading = creature.heading();
|
||||
cmd._equipment.clear();
|
||||
|
||||
for (auto &pair : creature.equipment()) {
|
||||
cmd._equipment.push_back(pair.second->resRef());
|
||||
}
|
||||
|
||||
_server->send(client, cmd.bytes());
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendSetPlayerRoleCommand(const string &client, CreatureRole role) {
|
||||
Command cmd(CommandType::SetPlayerRole);
|
||||
cmd._role = role;
|
||||
|
||||
_server->send(client, cmd.bytes());
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendSetObjectTransformCommand(const string &tag, const glm::vec3 &position, float heading) {
|
||||
Command cmd(CommandType::SetObjectTransform);
|
||||
cmd._tag = tag;
|
||||
cmd._position = position;
|
||||
cmd._heading = heading;
|
||||
|
||||
if (_mode == MultiplayerMode::Client) {
|
||||
_client->send(cmd.bytes());
|
||||
} else {
|
||||
_server->sendToAll(cmd.bytes());
|
||||
void MultiplayerGame::onObjectAnimationChanged(const Object &object, const string &anim, int flags, float speed) {
|
||||
if (shouldSendObjectUpdates(object.id())) {
|
||||
sendSetObjectAnimation(object.id(), anim, flags, speed);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendSetObjectAnimationCommand(const string &tag, const string &animation, int flags, float speed) {
|
||||
Command cmd(CommandType::SetObjectAnimation);
|
||||
cmd._tag = tag;
|
||||
cmd._animation = animation;
|
||||
cmd._animationFlags = flags;
|
||||
cmd._animationSpeed = speed;
|
||||
void MultiplayerGame::sendSetObjectAnimation(uint32_t objectId, const string &animation, int flags, float speed) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::SetObjectAnimation));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._objectId = objectId;
|
||||
cmd2._animation = animation;
|
||||
cmd2._animationFlags = flags;
|
||||
cmd2._animationSpeed = speed;
|
||||
|
||||
if (_mode == MultiplayerMode::Client) {
|
||||
_client->send(cmd.bytes());
|
||||
} else {
|
||||
_server->sendToAll(cmd.bytes());
|
||||
sendCommand(cmd);
|
||||
}
|
||||
|
||||
void MultiplayerGame::onCreatureMovementTypeChanged(const MultiplayerCreature &creature, MovementType type) {
|
||||
if (shouldSendObjectUpdates(creature.id())) {
|
||||
sendSetCreatureMovementType(creature.id(), type);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendSetCreatureMovementTypeCommand(const string &tag, MovementType type) {
|
||||
Command cmd(CommandType::SetCreatureMovementType);
|
||||
cmd._tag = tag;
|
||||
cmd._movementType = type;
|
||||
void MultiplayerGame::sendSetCreatureMovementType(uint32_t objectId, MovementType type) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::SetCreatureMovementType));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._objectId = objectId;
|
||||
cmd2._movementType = type;
|
||||
|
||||
if (_mode == MultiplayerMode::Client) {
|
||||
_client->send(cmd.bytes());
|
||||
} else {
|
||||
_server->sendToAll(cmd.bytes());
|
||||
sendCommand(cmd);
|
||||
}
|
||||
|
||||
void MultiplayerGame::onDoorOpen(const MultiplayerDoor &door, const shared_ptr<Object> &trigerrer) {
|
||||
uint32_t triggerrerId(trigerrer ? trigerrer->id() : 0);
|
||||
if (shouldSendObjectUpdates(triggerrerId)) {
|
||||
sendSetDoorOpen(door.id(), triggerrerId);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerGame::sendSetDoorOpenCommand(uint32_t id, const string &trigerrer) {
|
||||
Command cmd(CommandType::SetDoorOpen);
|
||||
cmd._objectId = id;
|
||||
cmd._trigerrer = trigerrer;
|
||||
cmd._open = true;
|
||||
void MultiplayerGame::sendSetDoorOpen(uint32_t objectId, uint32_t triggerrer) {
|
||||
shared_ptr<net::Command> cmd(makeCommand(CommandType::SetDoorOpen));
|
||||
Command &cmd2 = static_cast<Command &>(*cmd);
|
||||
cmd2._objectId = objectId;
|
||||
cmd2._triggerrer = triggerrer;
|
||||
cmd2._open = true;
|
||||
|
||||
if (_mode == MultiplayerMode::Client) {
|
||||
_client->send(cmd.bytes());
|
||||
} else {
|
||||
_server->sendToAll(cmd.bytes());
|
||||
}
|
||||
sendCommand(cmd);
|
||||
}
|
||||
|
||||
const shared_ptr<Module> MultiplayerGame::makeModule(const string &name) {
|
||||
return shared_ptr<Module>(new MultiplayerModule(name, _mode, _version, _opts.graphics, this));
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "../game.h"
|
||||
|
@ -39,31 +41,41 @@ public:
|
|||
const Options &opts);
|
||||
|
||||
private:
|
||||
uint32_t _cmdCounter { 0 };
|
||||
MultiplayerMode _mode { MultiplayerMode::Server };
|
||||
std::recursive_mutex _syncMutex;
|
||||
std::unique_ptr<net::Client> _client;
|
||||
std::unique_ptr<net::Server> _server;
|
||||
std::recursive_mutex _commandsMutex;
|
||||
std::queue<Command> _commands;
|
||||
std::recursive_mutex _syncMutex;
|
||||
|
||||
// Commands
|
||||
std::queue<Command> _commandsIn;
|
||||
std::recursive_mutex _commandsInMutex;
|
||||
|
||||
// Game overrides
|
||||
void configure() override;
|
||||
const std::shared_ptr<Module> makeModule(const std::string &name) override;
|
||||
void update() override;
|
||||
void loadNextModule() override;
|
||||
const std::shared_ptr<Module> makeModule(const std::string &name) override;
|
||||
|
||||
// IMultiplayerCallbacks overrides
|
||||
void onObjectTransformChanged(const Object &object, const glm::vec3 &position, float heading) override;
|
||||
void onObjectAnimationChanged(const Object &object, const std::string &anim, int flags, float speed) override;
|
||||
void onCreatureMovementTypeChanged(const MultiplayerCreature &creature, MovementType type) override;
|
||||
|
||||
bool shouldSendObjectUpdates(const std::string &tag) const;
|
||||
std::shared_ptr<net::Command> makeCommand(net::CommandType type);
|
||||
bool shouldSendObjectUpdates(uint32_t objectId) const;
|
||||
void synchronizeClient(const std::string &tag);
|
||||
void sendLoadModuleCommand(const std::string &client, const std::string &module);
|
||||
void sendLoadCreatureCommand(const std::string &client, CreatureRole role, const Creature &creature);
|
||||
void sendSetPlayerRoleCommand(const std::string &client, CreatureRole role);
|
||||
void sendSetObjectTransformCommand(const std::string &tag, const glm::vec3 &position, float heading);
|
||||
void sendSetObjectAnimationCommand(const std::string &tag, const std::string &animation, int flags, float speed);
|
||||
void sendSetCreatureMovementTypeCommand(const std::string &tag, MovementType type);
|
||||
void sendSetDoorOpenCommand(uint32_t objectId, const std::string &trigerrer);
|
||||
|
||||
void sendLoadModule(const std::string &client, const std::string &module);
|
||||
void sendLoadCreature(const std::string &client, CreatureRole role, const Creature &creature);
|
||||
void sendSetPlayerRole(const std::string &client, CreatureRole role);
|
||||
void sendSetObjectTransform(uint32_t objectId, const glm::vec3 &position, float heading);
|
||||
void sendSetObjectAnimation(uint32_t objectId, const std::string &animation, int flags, float speed);
|
||||
void sendSetCreatureMovementType(uint32_t objectId, MovementType type);
|
||||
void sendSetDoorOpen(uint32_t objectId, uint32_t triggerrer);
|
||||
|
||||
void sendCommand(const std::shared_ptr<net::Command> &command);
|
||||
void sendCommand(const std::string &client, const std::shared_ptr<net::Command> &command);
|
||||
|
||||
void onClientConnected(const std::string tag);
|
||||
void onClientDisconnected(const std::string tag);
|
||||
|
|
70
src/game/multiplayer/util.cpp
Normal file
70
src/game/multiplayer/util.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright © 2020 Vsevolod Kremianskii
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace game {
|
||||
|
||||
static map<net::CommandType, string> g_cmdDesc = {
|
||||
{ net::CommandType::LoadModule, "LoadModule" },
|
||||
{ net::CommandType::LoadCreature, "LoadCreature" },
|
||||
{ net::CommandType::SetPlayerRole, "SetPlayerRole" },
|
||||
{ net::CommandType::SetObjectTransform, "SetObjectTransform" },
|
||||
{ net::CommandType::SetObjectAnimation, "SetObjectAnimation" },
|
||||
{ net::CommandType::SetCreatureMovementType, "SetCreatureMovementType" },
|
||||
{ net::CommandType::SetDoorOpen, "SetDoorOpen" }
|
||||
};
|
||||
|
||||
static const string &describeCommandType(net::CommandType type) {
|
||||
auto desc = g_cmdDesc.find(type);
|
||||
if (desc == g_cmdDesc.end()) {
|
||||
auto pair = g_cmdDesc.insert(make_pair(type, to_string(static_cast<int>(type))));
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
return desc->second;
|
||||
}
|
||||
|
||||
string describeCommand(const Command &command) {
|
||||
string desc(describeCommandType(command.type()));
|
||||
switch (command.type()) {
|
||||
case net::CommandType::SetObjectTransform:
|
||||
case net::CommandType::SetObjectAnimation:
|
||||
case net::CommandType::SetCreatureMovementType:
|
||||
desc += str(boost::format(" {id=%d,objectId=%d}") % command.id() % command.objectId());
|
||||
break;
|
||||
default:
|
||||
desc += str(boost::format(" {id=%d}") % command.id());
|
||||
break;
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
|
||||
} // namespace reone
|
32
src/game/multiplayer/util.h
Normal file
32
src/game/multiplayer/util.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright © 2020 Vsevolod Kremianskii
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "command.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace game {
|
||||
|
||||
std::string describeCommand(const Command &cmd);
|
||||
|
||||
} // namespace game
|
||||
|
||||
} // namespace reone
|
|
@ -66,7 +66,7 @@ protected:
|
|||
glm::mat4 _transform { 1.0f };
|
||||
std::shared_ptr<render::ModelInstance> _model;
|
||||
std::shared_ptr<render::Walkmesh> _walkmesh;
|
||||
bool _synchronize { true };
|
||||
bool _synchronize { false };
|
||||
|
||||
Object(uint32_t id);
|
||||
Object(Object &&) = default;
|
||||
|
|
|
@ -71,9 +71,9 @@ void Client::stop() {
|
|||
}
|
||||
}
|
||||
|
||||
void Client::send(const ByteArray &data) {
|
||||
void Client::send(const shared_ptr<Command> &command) {
|
||||
assert(_connection);
|
||||
_connection->send(data);
|
||||
_connection->send(command);
|
||||
}
|
||||
|
||||
void Client::setOnCommandReceived(const function<void(const ByteArray &)> &fn) {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
#include "connection.h"
|
||||
#include "command.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
@ -36,7 +37,7 @@ public:
|
|||
void start(const std::string &address, int port);
|
||||
void stop();
|
||||
|
||||
void send(const ByteArray &data);
|
||||
void send(const std::shared_ptr<Command> &command);
|
||||
|
||||
void setOnCommandReceived(const std::function<void(const ByteArray &)> &fn);
|
||||
|
||||
|
|
41
src/net/command.cpp
Normal file
41
src/net/command.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright © 2020 Vsevolod Kremianskii
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "command.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace net {
|
||||
|
||||
Command::Command(uint32_t id, CommandType type) : _id(id), _type(type) {
|
||||
}
|
||||
|
||||
uint32_t Command::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
CommandType Command::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
uint32_t Command::objectId() const {
|
||||
return _objectId;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
|
||||
} // namespace reone
|
47
src/net/command.h
Normal file
47
src/net/command.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright © 2020 Vsevolod Kremianskii
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/types.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace net {
|
||||
|
||||
class Command {
|
||||
public:
|
||||
Command() = default;
|
||||
Command(uint32_t id, CommandType type);
|
||||
|
||||
virtual ByteArray bytes() const = 0;
|
||||
|
||||
uint32_t id() const;
|
||||
CommandType type() const;
|
||||
uint32_t objectId() const;
|
||||
|
||||
protected:
|
||||
uint32_t _id { 0 };
|
||||
CommandType _type { CommandType::LoadModule };
|
||||
uint32_t _objectId { 0 };
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
} // namespace reone
|
|
@ -46,13 +46,13 @@ void Connection::open() {
|
|||
void Connection::handleRead(size_t bytesRead, const boost::system::error_code &ec) {
|
||||
if (ec) {
|
||||
if (ec != boost::asio::error::eof) {
|
||||
error("TCP: read failed: " + ec.message());
|
||||
error("Connection: read failed: " + ec.message());
|
||||
}
|
||||
if (_onAbort) _onAbort(_tag);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(boost::format("TCP: received %d bytes from %s") % bytesRead % _socket->remote_endpoint(), 3);
|
||||
debug(boost::format("Connection: %d bytes received from %s") % bytesRead % _socket->remote_endpoint(), 3);
|
||||
|
||||
if (_cmdLength == 0) {
|
||||
char buf[2];
|
||||
|
@ -61,7 +61,7 @@ void Connection::handleRead(size_t bytesRead, const boost::system::error_code &e
|
|||
_cmdLength = *reinterpret_cast<const uint16_t *>(buf);
|
||||
|
||||
if (_cmdLength == 0) {
|
||||
error("TCP: invalid command length: " + to_string(_cmdLength));
|
||||
error("Connection: invalid command length: " + to_string(_cmdLength));
|
||||
if (_onAbort) _onAbort(_tag);
|
||||
return;
|
||||
}
|
||||
|
@ -100,29 +100,69 @@ void Connection::close() {
|
|||
}
|
||||
}
|
||||
|
||||
void Connection::send(const ByteArray &data) {
|
||||
ByteArray data2(data);
|
||||
int cmdLength = static_cast<int>(data2.size());
|
||||
data2.insert(data2.begin(), (cmdLength >> 8) & 0xff);
|
||||
data2.insert(data2.begin(), cmdLength & 0xff);
|
||||
void Connection::send(const shared_ptr<Command> &command) {
|
||||
lock_guard<recursive_mutex> lock(_cmdOutMutex);
|
||||
eraseSameCommands(*command);
|
||||
_cmdOut.push_back(command);
|
||||
|
||||
if (_cmdOut.size() == 1) {
|
||||
doSend(*command);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::eraseSameCommands(const Command &command) {
|
||||
switch (command.type()) {
|
||||
case net::CommandType::SetObjectTransform:
|
||||
case net::CommandType::SetObjectAnimation:
|
||||
case net::CommandType::SetCreatureMovementType:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
auto cmdToErase = remove_if(
|
||||
_cmdOut.begin(),
|
||||
_cmdOut.end(),
|
||||
[&command](const shared_ptr<Command> &item) { return item->type() == command.type() && item->objectId() == command.objectId(); });
|
||||
|
||||
_cmdOut.erase(cmdToErase, _cmdOut.end());
|
||||
}
|
||||
|
||||
void Connection::doSend(const Command &command) {
|
||||
ByteArray data(command.bytes());
|
||||
int cmdLength = static_cast<int>(data.size());
|
||||
data.insert(data.begin(), (cmdLength >> 8) & 0xff);
|
||||
data.insert(data.begin(), cmdLength & 0xff);
|
||||
|
||||
shared_ptr<boost::asio::streambuf> buffer(new boost::asio::streambuf());
|
||||
ostream out(buffer.get());
|
||||
out.write(&data2[0], data2.size());
|
||||
out.write(&data[0], data.size());
|
||||
|
||||
boost::asio::async_write(
|
||||
*_socket,
|
||||
*buffer,
|
||||
bind(&Connection::handleWrite, this, buffer, _1));
|
||||
bind(&Connection::handleWrite, this, command.id(), buffer, _1));
|
||||
}
|
||||
|
||||
void Connection::handleWrite(shared_ptr<boost::asio::streambuf> &buffer, const boost::system::error_code &ec) {
|
||||
void Connection::handleWrite(uint32_t commandId, shared_ptr<boost::asio::streambuf> &buffer, const boost::system::error_code &ec) {
|
||||
buffer.reset();
|
||||
|
||||
if (ec) {
|
||||
error("TCP: write failed: " + ec.message());
|
||||
error("Connection: write failed: " + ec.message());
|
||||
if (_onAbort) _onAbort(_tag);
|
||||
}
|
||||
lock_guard<recursive_mutex> lock(_cmdOutMutex);
|
||||
auto command = find_if(
|
||||
_cmdOut.begin(),
|
||||
_cmdOut.end(),
|
||||
[&commandId](const shared_ptr<Command> &cmd) { return cmd->id() == commandId; });
|
||||
|
||||
if (command != _cmdOut.end()) {
|
||||
_cmdOut.erase(command);
|
||||
}
|
||||
if (!_cmdOut.empty()) {
|
||||
doSend(*_cmdOut.front());
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::setTag(const string &tag) {
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
|
@ -25,6 +27,7 @@
|
|||
|
||||
#include "../core/types.h"
|
||||
|
||||
#include "command.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace reone {
|
||||
|
@ -39,7 +42,7 @@ public:
|
|||
void open();
|
||||
void close();
|
||||
|
||||
void send(const ByteArray &data);
|
||||
void send(const std::shared_ptr<Command> &command);
|
||||
|
||||
void setTag(const std::string &tag);
|
||||
|
||||
|
@ -53,14 +56,18 @@ private:
|
|||
std::string _tag;
|
||||
boost::asio::streambuf _readBuffer;
|
||||
int _cmdLength { 0 };
|
||||
std::list<std::shared_ptr<Command>> _cmdOut;
|
||||
std::recursive_mutex _cmdOutMutex;
|
||||
std::function<void(const std::string &)> _onAbort;
|
||||
std::function<void(const ByteArray &)> _onCommandReceived;
|
||||
|
||||
Connection(const Connection &) = delete;
|
||||
Connection &operator=(const Connection &) = delete;
|
||||
|
||||
void eraseSameCommands(const Command &command);
|
||||
void doSend(const Command &command);
|
||||
void handleRead(size_t bytesRead, const boost::system::error_code &ec);
|
||||
void handleWrite(std::shared_ptr<boost::asio::streambuf> &buffer, const boost::system::error_code &ec);
|
||||
void handleWrite(uint32_t commandId, std::shared_ptr<boost::asio::streambuf> &buffer, const boost::system::error_code &ec);
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
|
|
@ -55,7 +55,9 @@ void Server::handleAccept(shared_ptr<tcp::socket> &socket, const boost::system::
|
|||
unique_ptr<Connection> client(new Connection(socket));
|
||||
client->setTag(tag);
|
||||
client->setOnAbort([this, &client](const string &tag) { stopClient(tag); });
|
||||
client->setOnCommandReceived(_onCommandReceived);
|
||||
client->setOnCommandReceived([this, tag](const ByteArray &data) {
|
||||
_onCommandReceived(tag, data);
|
||||
});
|
||||
client->open();
|
||||
_clients.insert(make_pair(tag, move(client)));
|
||||
|
||||
|
@ -101,18 +103,18 @@ void Server::stop() {
|
|||
_clients.clear();
|
||||
}
|
||||
|
||||
void Server::send(const string &tag, const ByteArray &data) {
|
||||
void Server::send(const string &tag, const shared_ptr<Command> &command) {
|
||||
auto it = _clients.find(tag);
|
||||
if (it == _clients.end()) {
|
||||
warn("TCP: invalid client: " + tag);
|
||||
return;
|
||||
}
|
||||
it->second->send(data);
|
||||
it->second->send(command);
|
||||
}
|
||||
|
||||
void Server::sendToAll(const ByteArray &data) {
|
||||
void Server::sendToAll(const shared_ptr<Command> &command) {
|
||||
for (auto &client : _clients) {
|
||||
client.second->send(data);
|
||||
client.second->send(command);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +130,7 @@ void Server::setOnClientDisconnected(const function<void(const string &)> &fn) {
|
|||
_onClientDisconnected = fn;
|
||||
}
|
||||
|
||||
void Server::setOnCommandReceived(const function<void(const ByteArray &)> &fn) {
|
||||
void Server::setOnCommandReceived(const function<void(const string &, const ByteArray &)> &fn) {
|
||||
_onCommandReceived = fn;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include "connection.h"
|
||||
#include "command.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace reone {
|
||||
|
@ -41,15 +42,15 @@ public:
|
|||
void start(int port);
|
||||
void stop();
|
||||
|
||||
void send(const std::string &client, const ByteArray &data);
|
||||
void sendToAll(const ByteArray &data);
|
||||
void send(const std::string &client, const std::shared_ptr<Command> &command);
|
||||
void sendToAll(const std::shared_ptr<Command> &command);
|
||||
|
||||
const ServerClients &clients() const;
|
||||
|
||||
// Callbacks
|
||||
void setOnClientConnected(const std::function<void(const std::string &)> &fn);
|
||||
void setOnClientDisconnected(const std::function<void(const std::string &)> &fn);
|
||||
void setOnCommandReceived(const std::function<void(const ByteArray &)> &fn);
|
||||
void setOnCommandReceived(const std::function<void(const std::string &, const ByteArray &)> &fn);
|
||||
|
||||
private:
|
||||
boost::asio::io_service _service;
|
||||
|
@ -60,7 +61,7 @@ private:
|
|||
// Callbacks
|
||||
std::function<void(const std::string &)> _onClientConnected;
|
||||
std::function<void(const std::string &)> _onClientDisconnected;
|
||||
std::function<void(const ByteArray &)> _onCommandReceived;
|
||||
std::function<void(const std::string &, const ByteArray &)> _onCommandReceived;
|
||||
|
||||
Server(const Server &) = delete;
|
||||
Server &operator=(const Server &) = delete;
|
||||
|
|
|
@ -23,6 +23,16 @@ namespace reone {
|
|||
|
||||
namespace net {
|
||||
|
||||
enum class CommandType {
|
||||
LoadModule,
|
||||
LoadCreature,
|
||||
SetPlayerRole,
|
||||
SetObjectTransform,
|
||||
SetObjectAnimation,
|
||||
SetCreatureMovementType,
|
||||
SetDoorOpen
|
||||
};
|
||||
|
||||
struct NetworkOptions {
|
||||
std::string host;
|
||||
int port { 0 };
|
||||
|
|
Loading…
Reference in a new issue