refactor: Remove most of the multiplayer game logic

I want to come up with a better architecture.
This commit is contained in:
Vsevolod Kremianskii 2020-10-19 21:38:01 +07:00
parent 4e1b8d8690
commit ab02686b3c
21 changed files with 70 additions and 1216 deletions

View file

@ -367,23 +367,14 @@ target_link_libraries(libgame PUBLIC libsystem)
## libmp static library
set(MP_HEADERS
src/mp/area.h
src/mp/callbacks.h
src/mp/command.h
src/mp/creature.h
src/mp/door.h
src/mp/game.h
src/mp/objectfactory.h
src/mp/types.h
src/mp/util.h)
set(MP_SOURCES
src/mp/area.cpp
src/mp/command.cpp
src/mp/creature.cpp
src/mp/door.cpp
src/mp/game.cpp
src/mp/objectfactory.cpp
src/mp/util.cpp)
add_library(libmp STATIC ${MP_HEADERS} ${MP_SOURCES})

View file

@ -78,7 +78,7 @@ void Game::initObjectFactory() {
}
int Game::run() {
initSubsystems();
init();
loadResources();
openMainMenu();
configure();
@ -86,12 +86,12 @@ int Game::run() {
_window.show();
runMainLoop();
deinitSubsystems();
deinit();
return 0;
}
void Game::initSubsystems() {
void Game::init() {
_window.init();
_worldPipeline.init();
@ -496,7 +496,7 @@ void Game::onPlay(const CreatureConfiguration &config) {
loadModule(moduleName, party);
}
void Game::deinitSubsystems() {
void Game::deinit() {
Jobs.deinit();
Routines.deinit();
TheAudioPlayer.deinit();

View file

@ -93,6 +93,7 @@ protected:
bool _pickDialogReplyEnabled { true };
virtual void initObjectFactory();
virtual void init();
virtual void configure();
virtual void configureModule();
virtual void update();
@ -145,8 +146,7 @@ private:
// Initialization
void initGameVersion();
void initSubsystems();
void deinitSubsystems();
void deinit();
// END Initialization

View file

@ -1,176 +0,0 @@
/*
* 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 "area.h"
#include "../game/object/objectfactory.h"
using namespace std;
using namespace reone::game;
using namespace reone::net;
using namespace reone::render;
using namespace reone::resource;
using namespace reone::scene;
namespace reone {
namespace mp {
MultiplayerArea::MultiplayerArea(
uint32_t id,
GameVersion version,
MultiplayerMode mode,
ObjectFactory *objectFactory,
SceneGraph *sceneGraph,
const GraphicsOptions &opts,
IMultiplayerCallbacks *callbacks
) :
Area(id, version, objectFactory, sceneGraph, opts), _callbacks(callbacks) {
_scriptsEnabled = mode == MultiplayerMode::Server;
}
void MultiplayerArea::execute(const Command &cmd) {
switch (cmd.type()) {
case CommandType::LoadCreature:
executeLoadCreature(cmd);
break;
case CommandType::SetPlayerRole:
executeSetPlayerRole(cmd);
break;
case CommandType::SetObjectTransform:
executeSetObjectTransform(cmd);
break;
case CommandType::SetObjectAnimation:
executeSetObjectAnimation(cmd);
break;
case CommandType::SetCreatureMovementType:
executeSetCreatureMovementType(cmd);
break;
case CommandType::SetCreatureTalking:
executeSetCreatureTalking(cmd);
break;
case CommandType::SetDoorOpen:
executeSetDoorOpen(cmd);
break;
default:
break;
}
}
void MultiplayerArea::executeLoadCreature(const Command &cmd) {
shared_ptr<Creature> creature(new MultiplayerCreature(cmd.objectId(), _objectFactory, _sceneGraph, _callbacks));
creature->setTag(cmd.tag());
for (auto &item : cmd.equipment()) {
creature->equip(item);
}
CreatureConfiguration config;
config.appearance = cmd.appearance();
creature->load(move(config));
creature->setPosition(cmd.position());
creature->setHeading(cmd.heading());
switch (cmd.role()) {
case CreatureRole::PartyLeader:
_partyLeader = creature;
break;
case CreatureRole::PartyMember1:
_partyMember1 = creature;
break;
case CreatureRole::PartyMember2:
_partyMember2 = creature;
break;
default:
break;
}
landObject(*creature);
add(creature);
}
void MultiplayerArea::executeSetPlayerRole(const Command &cmd) {
switch (cmd.role()) {
case CreatureRole::PartyLeader:
_player = _partyLeader;
break;
case CreatureRole::PartyMember1:
_player = _partyMember1;
break;
case CreatureRole::PartyMember2:
_player = _partyMember2;
break;
default:
break;
}
if (_onPlayerChanged) {
_onPlayerChanged();
}
}
void MultiplayerArea::executeSetObjectTransform(const Command &cmd) {
shared_ptr<SpatialObject> object(find(cmd.objectId()));
if (object) {
object->setPosition(cmd.position());
object->setHeading(cmd.heading());
}
}
void MultiplayerArea::executeSetObjectAnimation(const Command &cmd) {
shared_ptr<SpatialObject> object(find(cmd.objectId()));
if (object) {
object->playAnimation(cmd.animation(), cmd.animationFlags());
}
}
void MultiplayerArea::executeSetCreatureMovementType(const Command &cmd) {
shared_ptr<SpatialObject> creature(find(cmd.objectId()));
if (creature) {
static_cast<Creature &>(*creature).setMovementType(cmd.movementType());
}
}
void MultiplayerArea::executeSetCreatureTalking(const Command &cmd) {
shared_ptr<SpatialObject> creature(find(cmd.objectId()));
if (creature) {
static_cast<Creature &>(*creature).setTalking(cmd.talking());
}
}
void MultiplayerArea::executeSetDoorOpen(const Command &cmd) {
shared_ptr<Object> door(find(cmd.objectId()));
shared_ptr<Object> trigerrer(find(cmd.triggerrer()));
if (door) {
static_cast<Door &>(*door).open(trigerrer);
}
}
const shared_ptr<Object> MultiplayerArea::findCreatureByClientTag(const string &clientTag) const {
auto creatures = _objectsByType.find(ObjectType::Creature)->second;
auto it = find_if(
creatures.begin(),
creatures.end(),
[this, &clientTag](const shared_ptr<Object> &o) { return static_cast<MultiplayerCreature &>(*o).clientTag() == clientTag; });
return it == creatures.end() ? nullptr : *it;
}
} // namespace mp
} // namespace reone

View file

@ -1,59 +0,0 @@
/*
* 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 "../game/object/area.h"
#include "command.h"
#include "creature.h"
#include "types.h"
namespace reone {
namespace mp {
class MultiplayerArea : public game::Area {
public:
MultiplayerArea(
uint32_t id,
resource::GameVersion version,
MultiplayerMode mode,
game::ObjectFactory *objectFactory,
scene::SceneGraph *sceneGraph,
const render::GraphicsOptions &opts,
IMultiplayerCallbacks *callbacks);
void execute(const Command &cmd);
const std::shared_ptr<Object> findCreatureByClientTag(const std::string &clientTag) const;
private:
IMultiplayerCallbacks *_callbacks { nullptr };
void executeLoadCreature(const Command &cmd);
void executeSetPlayerRole(const Command &cmd);
void executeSetObjectTransform(const Command &cmd);
void executeSetObjectAnimation(const Command &cmd);
void executeSetCreatureMovementType(const Command &cmd);
void executeSetCreatureTalking(const Command &cmd);
void executeSetDoorOpen(const Command &cmd);
};
} // namespace mp
} // namespace reone

View file

@ -1,38 +0,0 @@
/*
* 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 "creature.h"
#include "door.h"
namespace reone {
namespace mp {
class IMultiplayerCallbacks {
public:
virtual void onObjectTransformChanged(const game::Object &object, const glm::vec3 &position, float heading) = 0;
virtual void onObjectAnimationChanged(const game::Object &object, const std::string &anim, int flags, float speed) = 0;
virtual void onCreatureMovementTypeChanged(const MultiplayerCreature &creature, game::MovementType type) = 0;
virtual void onDoorOpen(const MultiplayerDoor &door, const std::shared_ptr<game::Object> &trigerrer) = 0;
virtual void onCreatureTalkingChanged(const MultiplayerCreature &creature, bool talking) = 0;
};
} // namespace mp
} // namespace reone

View file

@ -17,12 +17,12 @@
#include "command.h"
#include <cstring>
#include <stdexcept>
#include <string>
using namespace std;
using namespace reone::game;
namespace reone {
namespace mp {
@ -97,75 +97,10 @@ void Command::load(const ByteArray &data) {
_type = static_cast<CommandType>(data[0]);
int offset = 1;
int equipmentCount = 0;
_id = getUint32(data, offset);
switch (_type) {
case CommandType::LoadModule:
_module = getString(data, offset);
break;
case 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);
_position.y = getFloat(data, offset);
_position.z = getFloat(data, offset);
_heading = getFloat(data, offset);
equipmentCount = getUint8(data, offset);
for (int i = 0; i < equipmentCount; ++i) {
_equipment.push_back(getString(data, offset));
}
break;
case CommandType::SetPlayerRole:
_role = static_cast<CreatureRole>(getUint8(data, offset));
break;
case 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:
_objectId = getUint16(data, offset);
_animationFlags = getUint8(data, offset);
_animation = getString(data, offset);
break;
case CommandType::SetCreatureMovementType:
_objectId = getUint16(data, offset);
_movementType = static_cast<MovementType>(getUint8(data, offset));
break;
case CommandType::SetCreatureTalking:
_objectId = getUint16(data, offset);
_talking = getUint8(data, offset);
break;
case CommandType::SetDoorOpen:
_open = getUint8(data, offset);
_objectId = getUint16(data, offset);
_triggerrer = getUint16(data, offset);
break;
case CommandType::StartDialog:
_resRef = getString(data, offset);
break;
case CommandType::PickDialogReply:
_replyIndex = getUint8(data, offset);
break;
case CommandType::FinishDialog:
break;
default:
throw runtime_error("Command: unsupported type: " + to_string(static_cast<int>(_type)));
}
@ -176,71 +111,7 @@ ByteArray Command::getBytes() const {
putUint8(static_cast<uint8_t>(_type), data);
putUint32(_id, data);
switch (static_cast<CommandType>(_type)) {
case CommandType::LoadModule:
putString(_module, data);
break;
case CommandType::LoadCreature:
putUint8(static_cast<uint8_t>(_role), data);
putUint16(_objectId, data);
putString(_tag, data);
putUint16(_appearance, data);
putFloat(_position.x, data);
putFloat(_position.y, data);
putFloat(_position.z, data);
putFloat(_heading, data);
putUint8(static_cast<uint8_t>(_equipment.size()), data);
for (auto &item : _equipment) {
putString(item, data);
}
break;
case CommandType::SetPlayerRole:
putUint8(static_cast<uint8_t>(_role), data);
break;
case CommandType::SetObjectTransform:
putUint16(_objectId, data);
putFloat(_position.x, data);
putFloat(_position.y, data);
putFloat(_position.z, data);
putFloat(_heading, data);
break;
case CommandType::SetObjectAnimation:
putUint16(_objectId, data);
putUint8(_animationFlags, data);
putString(_animation, data);
break;
case CommandType::SetCreatureMovementType:
putUint16(_objectId, data);
putUint8(static_cast<uint8_t>(_movementType), data);
break;
case CommandType::SetCreatureTalking:
putUint16(_objectId, data);
putUint8(_talking, data);
break;
case CommandType::SetDoorOpen:
putUint8(_open, data);
putUint16(_objectId, data);
putUint16(_triggerrer, data);
break;
case CommandType::StartDialog:
putString(_resRef, data);
break;
case CommandType::PickDialogReply:
putUint8(_replyIndex, data);
break;
case CommandType::FinishDialog:
break;
switch (_type) {
default:
throw runtime_error("Command: unsupported type: " + to_string(static_cast<int>(_type)));
}
@ -252,70 +123,6 @@ CommandType Command::type() const {
return _type;
}
uint32_t Command::objectId() const {
return _objectId;
}
const string &Command::module() const {
return _module;
}
const string &Command::tag() const {
return _tag;
}
CreatureRole Command::role() const {
return _role;
}
int Command::appearance() const {
return _appearance;
}
const vector<string> &Command::equipment() const {
return _equipment;
}
const glm::vec3 &Command::position() const {
return _position;
}
float Command::heading() const {
return _heading;
}
const string &Command::animation() const {
return _animation;
}
int Command::animationFlags() const {
return _animationFlags;
}
MovementType Command::movementType() const {
return _movementType;
}
bool Command::talking() const {
return _talking;
}
bool Command::open() const {
return _open;
}
uint32_t Command::triggerrer() const {
return _triggerrer;
}
const string &Command::resRef() const {
return _resRef;
}
uint32_t Command::replyIndex() const {
return _replyIndex;
}
} // namespace mp
} // namespace reone

View file

@ -17,11 +17,6 @@
#pragma once
#include <string>
#include <vector>
#include "../game/types.h"
#include "../system/types.h"
#include "../system/net/command.h"
namespace reone {
@ -29,17 +24,7 @@ namespace reone {
namespace mp {
enum class CommandType {
LoadModule,
LoadCreature,
SetPlayerRole,
SetObjectTransform,
SetObjectAnimation,
SetCreatureMovementType,
SetCreatureTalking,
SetDoorOpen,
StartDialog,
PickDialogReply,
FinishDialog
None
};
class Command : public net::Command {
@ -52,44 +37,9 @@ public:
ByteArray getBytes() const override;
CommandType type() const;
uint32_t objectId() const;
const std::string &module() const;
const std::string &tag() const;
game::CreatureRole role() const;
int appearance() const;
const std::vector<std::string> &equipment() const;
const glm::vec3 &position() const;
float heading() const;
const std::string &animation() const;
int animationFlags() const;
game::MovementType movementType() const;
bool talking() const;
bool open() const;
uint32_t triggerrer() const;
const std::string &resRef() const;
uint32_t replyIndex() const;
private:
CommandType _type { CommandType::LoadModule };
uint32_t _objectId { 0 };
std::string _module;
std::string _tag;
game::CreatureRole _role { game::CreatureRole::None };
int _appearance { 0 };
std::vector<std::string> _equipment;
glm::vec3 _position { 0.0f };
float _heading { 0.0f };
std::string _animation;
int _animationFlags { 0 };
float _animationSpeed { 1.0f };
game::MovementType _movementType { game::MovementType::None };
bool _talking { false };
bool _open { false };
uint32_t _triggerrer { 0 };
std::string _resRef;
uint32_t _replyIndex { 0 };
friend class MultiplayerGame;
CommandType _type { CommandType::None };
};
} // namespace mp

View file

@ -1,74 +0,0 @@
/*
* 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 "creature.h"
#include "callbacks.h"
using namespace std;
using namespace reone::game;
using namespace reone::render;
using namespace reone::scene;
namespace reone {
namespace mp {
MultiplayerCreature::MultiplayerCreature(uint32_t id, ObjectFactory *objectFactory, SceneGraph *sceneGraph, IMultiplayerCallbacks *callbacks) :
Creature(id, objectFactory, sceneGraph), _callbacks(callbacks) {
}
void MultiplayerCreature::setClientTag(const string &clientTag) {
_clientTag = clientTag;
}
bool MultiplayerCreature::isControlled() const {
return !_clientTag.empty();
}
const string &MultiplayerCreature::clientTag() const {
return _clientTag;
}
void MultiplayerCreature::playAnimation(const string &anim, int flags, float speed) {
SpatialObject::playAnimation(anim, flags);
_callbacks->onObjectAnimationChanged(*this, anim, flags, speed);
}
void MultiplayerCreature::updateTransform() {
SpatialObject::updateTransform();
_callbacks->onObjectTransformChanged(*this, _position, _heading);
}
void MultiplayerCreature::setMovementType(MovementType type) {
if (type == _movementType) return;
Creature::setMovementType(type);
_callbacks->onCreatureMovementTypeChanged(*this, type);
}
void MultiplayerCreature::setTalking(bool talking) {
if (talking == _talking) return;
Creature::setTalking(talking);
_callbacks->onCreatureTalkingChanged(*this, talking);
}
} // namespace mp
} // namespace reone

View file

@ -1,53 +0,0 @@
/*
* 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 "../game/object/creature.h"
namespace reone {
namespace mp {
class IMultiplayerCallbacks;
class MultiplayerCreature : public game::Creature {
public:
MultiplayerCreature(
uint32_t id,
game::ObjectFactory *objectFactory,
scene::SceneGraph *sceneGraph,
IMultiplayerCallbacks *callbacks);
void setClientTag(const std::string &clientTag);
bool isControlled() const;
const std::string &clientTag() const;
private:
IMultiplayerCallbacks *_callbacks { nullptr };
std::string _clientTag;
void playAnimation(const std::string &name, int flags, float speed) override;
void updateTransform() override;
void setMovementType(game::MovementType type) override;
void setTalking(bool talking) override;
};
} // namespace mp
} // namespace reone

View file

@ -1,42 +0,0 @@
/*
* 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 "door.h"
#include "callbacks.h"
using namespace std;
using namespace reone::game;
using namespace reone::scene;
namespace reone {
namespace mp {
MultiplayerDoor::MultiplayerDoor(uint32_t id, SceneGraph *sceneGraph, IMultiplayerCallbacks *callbacks) :
Door(id, sceneGraph), _callbacks(callbacks) {
}
void MultiplayerDoor::open(const shared_ptr<Object> &trigerrer) {
Door::open(trigerrer);
_callbacks->onDoorOpen(*this, trigerrer);
}
} // namespace mp
} // namespace reone

View file

@ -1,40 +0,0 @@
/*
* 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 "../game/object/door.h"
namespace reone {
namespace mp {
class IMultiplayerCallbacks;
class MultiplayerDoor : public game::Door {
public:
MultiplayerDoor(uint32_t id, scene::SceneGraph *sceneGraph, IMultiplayerCallbacks *callbacks);
void open(const std::shared_ptr<game::Object> &trigerrer) override;
private:
IMultiplayerCallbacks *_callbacks { nullptr };;
};
} // namespace mp
} // namespace reone

View file

@ -19,8 +19,6 @@
#include "../system/log.h"
#include "area.h"
#include "objectfactory.h"
#include "util.h"
using namespace std;
@ -38,15 +36,9 @@ namespace mp {
MultiplayerGame::MultiplayerGame(MultiplayerMode mode, const fs::path &path, const Options &opts) :
Game(path, opts), _mode(mode) {
_pickDialogReplyEnabled = _mode == MultiplayerMode::Server;
}
void MultiplayerGame::initObjectFactory() {
_objectFactory = unique_ptr<ObjectFactory>(new MultiplayerObjectFactory(_version, _mode, &_sceneGraph, this, _options.graphics));
}
void MultiplayerGame::configure() {
void MultiplayerGame::init() {
switch (_mode) {
case MultiplayerMode::Server:
_server = make_unique<Server>();
@ -54,112 +46,22 @@ void MultiplayerGame::configure() {
_server->setOnClientDisconnected(bind(&MultiplayerGame::onClientDisconnected, this, _1));
_server->setOnCommandReceived(bind(&MultiplayerGame::onCommandReceived, this, _2));
_server->start(_options.network.port);
Game::configure();
break;
case MultiplayerMode::Client:
_client.reset(new Client());
_client->setOnCommandReceived(bind(&MultiplayerGame::onCommandReceived, this, _1));
_client->start(_options.network.host, _options.network.port);
break;
default:
Game::configure();
break;
}
Game::init();
}
void MultiplayerGame::onClientConnected(const string tag) {
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(CommandType type) {
return shared_ptr<net::Command>(new Command(_cmdCounter++, type));
}
void MultiplayerGame::sendCommand(const string &client, const shared_ptr<net::Command> &command) {
_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->blueprint().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;
shared_ptr<Area> area(_module->area());
shared_ptr<Object> object(static_cast<MultiplayerArea &>(*area).findCreatureByClientTag(tag));
if (object) {
static_cast<MultiplayerCreature &>(*object).setClientTag("");
}
}
void MultiplayerGame::onCommandReceived(const ByteArray &data) {
@ -173,94 +75,27 @@ void MultiplayerGame::onCommandReceived(const ByteArray &data) {
}
void MultiplayerGame::update() {
lock_guard<recursive_mutex> lock(_commandsInMutex);
bool skip = false;
while (!_commandsIn.empty() && !skip) {
const Command &cmd = _commandsIn.front();
switch (cmd.type()) {
case CommandType::LoadModule:
if (!_module || _module->name() != cmd.module()) {
_nextModule = cmd.module();
}
_commandsIn.pop();
break;
default:
if (_module && _nextModule.empty()) {
switch (cmd.type()) {
case CommandType::StartDialog: {
shared_ptr<SpatialObject> object(_module->area()->find(cmd.objectId()));
startDialog(*object, cmd.resRef());
break;
}
case CommandType::PickDialogReply:
_dialogGui->pickReply(cmd.replyIndex());
break;
case CommandType::FinishDialog:
_screen = GameScreen::InGame;
break;
default:
static_cast<MultiplayerArea &>(*_module->area()).execute(cmd);
break;
}
_commandsIn.pop();
} else {
skip = true;
}
break;
}
}
processCommands();
Game::update();
}
void MultiplayerGame::loadNextModule() {
Game::loadNextModule();
if (_mode == MultiplayerMode::Server) {
for (auto &client : _server->clients()) {
synchronizeClient(client.first);
}
void MultiplayerGame::processCommands() {
lock_guard<recursive_mutex> lock(_commandsInMutex);
while (!_commandsIn.empty()) {
const Command &cmd = _commandsIn.front();
_commandsIn.pop();
}
}
void MultiplayerGame::onObjectTransformChanged(const Object &object, const glm::vec3 &position, float heading) {
if (shouldSendObjectUpdates(object.id())) {
sendSetObjectTransform(object.id(), position, heading);
}
unique_ptr<net::Command> MultiplayerGame::newCommand(CommandType type) {
return make_unique<Command>(_cmdCounter++, type);
}
bool MultiplayerGame::shouldSendObjectUpdates(uint32_t objectId) const {
if (!_module || !_module->loaded()) return false;
shared_ptr<Object> player(_module->area()->player());
switch (_mode) {
case MultiplayerMode::Server:
return true;
case MultiplayerMode::Client:
return player && objectId == player->id();
default:
break;
}
return false;
void MultiplayerGame::send(const string &client, const shared_ptr<net::Command> &command) {
_server->send(client, command);
}
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;
sendCommand(cmd);
}
void MultiplayerGame::sendCommand(const shared_ptr<net::Command> &command) {
void MultiplayerGame::send(const shared_ptr<net::Command> &command) {
switch (_mode) {
case MultiplayerMode::Server:
_server->sendToAll(command);
@ -271,114 +106,6 @@ void MultiplayerGame::sendCommand(const shared_ptr<net::Command> &command) {
}
}
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::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;
sendCommand(cmd);
}
void MultiplayerGame::onCreatureMovementTypeChanged(const MultiplayerCreature &creature, MovementType type) {
if (shouldSendObjectUpdates(creature.id())) {
sendSetCreatureMovementType(creature.id(), type);
}
}
void MultiplayerGame::onCreatureTalkingChanged(const MultiplayerCreature &creature, bool talking) {
if (shouldSendObjectUpdates(creature.id())) {
sendSetCreatureTalking(creature.id(), talking);
}
}
void MultiplayerGame::startDialog(SpatialObject &owner, const string &resRef) {
Game::startDialog(owner, resRef);
if (_mode == MultiplayerMode::Server) {
sendStartDialog(owner.id(), resRef);
}
}
void MultiplayerGame::onDialogReplyPicked(uint32_t index) {
if (_mode == MultiplayerMode::Client) return;
Game::onDialogReplyPicked(index);
sendPickDialogReply(index);
}
void MultiplayerGame::onDialogFinished() {
if (_mode == MultiplayerMode::Client) return;
Game::onDialogFinished();
sendFinishDialog();
}
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;
sendCommand(cmd);
}
void MultiplayerGame::sendSetCreatureTalking(uint32_t objectId, bool talking) {
shared_ptr<net::Command> cmd(makeCommand(CommandType::SetCreatureTalking));
Command &cmd2 = static_cast<Command &>(*cmd);
cmd2._objectId = objectId;
cmd2._talking = talking;
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::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;
sendCommand(cmd);
}
void MultiplayerGame::sendStartDialog(uint32_t ownerId, const string &resRef) {
shared_ptr<net::Command> cmd(makeCommand(CommandType::StartDialog));
Command &cmd2 = static_cast<Command &>(*cmd);
cmd2._objectId = ownerId;
cmd2._resRef = resRef;
sendCommand(cmd);
}
void MultiplayerGame::sendPickDialogReply(uint32_t index) {
shared_ptr<net::Command> cmd(makeCommand(CommandType::PickDialogReply));
Command &cmd2 = static_cast<Command &>(*cmd);
cmd2._replyIndex = index;
sendCommand(cmd);
}
void MultiplayerGame::sendFinishDialog() {
shared_ptr<net::Command> cmd(makeCommand(CommandType::FinishDialog));
sendCommand(cmd);
}
} // namespace mp
} // namespace reone

View file

@ -17,15 +17,12 @@
#pragma once
#include <list>
#include <map>
#include <mutex>
#include "../game/game.h"
#include "../system/net/client.h"
#include "../system/net/server.h"
#include "callbacks.h"
#include "command.h"
#include "types.h"
@ -33,7 +30,7 @@ namespace reone {
namespace mp {
class MultiplayerGame : public game::Game, private IMultiplayerCallbacks {
class MultiplayerGame : public game::Game {
public:
MultiplayerGame(
MultiplayerMode mode,
@ -41,11 +38,10 @@ public:
const game::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::unique_ptr<net::Client> _client;
uint32_t _cmdCounter { 0 };
// Commands
@ -54,50 +50,21 @@ private:
// END Commands
// Game overrides
void initObjectFactory() override;
void configure() override;
void init() override;
void update() override;
void loadNextModule() override;
void startDialog(game::SpatialObject &owner, const std::string &resRef) override;
void onDialogReplyPicked(uint32_t index) override;
void onDialogFinished() override;
// END Game overrides
void processCommands();
std::unique_ptr<net::Command> newCommand(CommandType type);
void send(const std::shared_ptr<net::Command> &command);
void send(const std::string &client, const std::shared_ptr<net::Command> &command);
// IMultiplayerCallbacks overrides
void onObjectTransformChanged(const game::Object &object, const glm::vec3 &position, float heading) override;
void onObjectAnimationChanged(const game::Object &object, const std::string &anim, int flags, float speed) override;
void onCreatureMovementTypeChanged(const MultiplayerCreature &creature, game::MovementType type) override;
void onCreatureTalkingChanged(const MultiplayerCreature &creature, bool talking) override;
// END IMultiplayerCallbacks overrides
std::shared_ptr<net::Command> makeCommand(CommandType type);
bool shouldSendObjectUpdates(uint32_t objectId) const;
void synchronizeClient(const std::string &tag);
void sendLoadModule(const std::string &client, const std::string &module);
void sendLoadCreature(const std::string &client, game::CreatureRole role, const game::Creature &creature);
void sendSetPlayerRole(const std::string &client, game::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, game::MovementType type);
void sendSetCreatureTalking(uint32_t objectId, bool talking);
void sendSetDoorOpen(uint32_t objectId, uint32_t triggerrer);
void sendStartDialog(uint32_t ownerId, const std::string &resRef);
void sendPickDialogReply(uint32_t index);
void sendFinishDialog();
void sendCommand(const std::shared_ptr<net::Command> &command);
void sendCommand(const std::string &client, const std::shared_ptr<net::Command> &command);
// Event handlers
void onClientConnected(const std::string tag);
void onClientDisconnected(const std::string tag);
void onCommandReceived(const ByteArray &data);
void onDoorOpen(const MultiplayerDoor &door, const std::shared_ptr<game::Object> &trigerrer) override;
// END Event handlers
};
} // namespace mp

View file

@ -1,59 +0,0 @@
/*
* 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 "objectfactory.h"
#include "area.h"
#include "creature.h"
#include "door.h"
using namespace std;
using namespace reone::game;
using namespace reone::render;
using namespace reone::resource;
using namespace reone::scene;
namespace reone {
namespace mp {
MultiplayerObjectFactory::MultiplayerObjectFactory(
GameVersion version,
MultiplayerMode mode,
SceneGraph *sceneGraph,
IMultiplayerCallbacks *callbacks,
const GraphicsOptions &opts
) :
ObjectFactory(version, sceneGraph, opts), _mode(mode), _callbacks(callbacks) {
}
unique_ptr<Area> MultiplayerObjectFactory::newArea() {
return make_unique<MultiplayerArea>(_counter++, _version, _mode, this, _sceneGraph, _options, _callbacks);
}
unique_ptr<Creature> MultiplayerObjectFactory::newCreature() {
return make_unique<MultiplayerCreature>(_counter++, this, _sceneGraph, _callbacks);
}
unique_ptr<Door> MultiplayerObjectFactory::newDoor() {
return make_unique<MultiplayerDoor>(_counter++, _sceneGraph, _callbacks);
}
} // namespace mp
} // namespace reone

View file

@ -1,49 +0,0 @@
/*
* 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 "../game/object/objectfactory.h"
#include "callbacks.h"
#include "types.h"
namespace reone {
namespace mp {
class MultiplayerObjectFactory : public game::ObjectFactory {
public:
MultiplayerObjectFactory(
resource::GameVersion version,
MultiplayerMode mode,
scene::SceneGraph *sceneGraph,
IMultiplayerCallbacks *callbacks,
const render::GraphicsOptions &opts);
std::unique_ptr<game::Area> newArea();
std::unique_ptr<game::Creature> newCreature();
std::unique_ptr<game::Door> newDoor();
private:
MultiplayerMode _mode { MultiplayerMode::None };
IMultiplayerCallbacks *_callbacks { nullptr };
};
} // namespace mp
} // namespace reone

View file

@ -23,35 +23,20 @@
using namespace std;
using namespace reone::game;
using namespace reone::net;
namespace reone {
namespace mp {
static map<CommandType, string> g_cmdDesc = {
{ CommandType::LoadModule, "LoadModule" },
{ CommandType::LoadCreature, "LoadCreature" },
{ CommandType::SetPlayerRole, "SetPlayerRole" },
{ CommandType::SetObjectTransform, "SetObjectTransform" },
{ CommandType::SetObjectAnimation, "SetObjectAnimation" },
{ CommandType::SetCreatureMovementType, "SetCreatureMovementType" },
{ CommandType::SetCreatureTalking, "SetCreatureTalking" },
{ CommandType::SetDoorOpen, "SetDoorOpen" },
{ CommandType::StartDialog, "StartDialog" },
{ CommandType::PickDialogReply, "PickDialogReply" },
{ CommandType::FinishDialog, "FinishDialog" }
};
static const string &describeCommandType(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;
static map<CommandType, string> descriptions;
auto maybeDescription = descriptions.find(type);
if (maybeDescription != descriptions.end()) {
return maybeDescription->second;
}
return desc->second;
auto inserted = descriptions.insert(make_pair(type, to_string(static_cast<int>(type))));
return inserted.first->second;
}
string describeCommand(const Command &command) {

View file

@ -134,7 +134,9 @@ void Connection::handleWrite(uint32_t commandId, shared_ptr<boost::asio::streamb
if (ec) {
error("Connection: write failed: " + ec.message());
if (_onAbort) _onAbort(_tag);
if (_onAbort) {
_onAbort(_tag);
}
}
lock_guard<recursive_mutex> lock(_cmdOutMutex);
auto command = find_if(
@ -150,14 +152,14 @@ void Connection::handleWrite(uint32_t commandId, shared_ptr<boost::asio::streamb
}
}
void Connection::setTag(const string &tag) {
_tag = tag;
}
const string &Connection::tag() const {
return _tag;
}
void Connection::setTag(const string &tag) {
_tag = tag;
}
void Connection::setOnAbort(const function<void(const string &)> &fn) {
_onAbort = fn;
}

View file

@ -18,8 +18,8 @@
#pragma once
#include <functional>
#include <list>
#include <mutex>
#include <vector>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/streambuf.hpp>
@ -44,23 +44,32 @@ public:
void send(const std::shared_ptr<Command> &command);
const std::string &tag() const;
void setTag(const std::string &tag);
const std::string &tag() const;
// Callbacks
void setOnAbort(const std::function<void(const std::string &)> &fn);
void setOnCommandReceived(const std::function<void(const ByteArray &)> &fn);
// END Callbacks
private:
std::shared_ptr<boost::asio::ip::tcp::socket> _socket;
std::string _tag;
boost::asio::streambuf _readBuffer;
int _cmdLength { 0 };
std::list<std::shared_ptr<Command>> _cmdOut;
std::vector<std::shared_ptr<Command>> _cmdOut;
std::recursive_mutex _cmdOutMutex;
// Callbacks
std::function<void(const std::string &)> _onAbort;
std::function<void(const ByteArray &)> _onCommandReceived;
// END Callbacks
Connection(const Connection &) = delete;
Connection &operator=(const Connection &) = delete;

View file

@ -110,12 +110,12 @@ void Server::stop() {
}
void Server::send(const string &tag, const shared_ptr<Command> &command) {
auto it = _clients.find(tag);
if (it == _clients.end()) {
auto maybeClient = _clients.find(tag);
if (maybeClient == _clients.end()) {
warn("TCP: invalid client: " + tag);
return;
}
it->second->send(command);
maybeClient->second->send(command);
}
void Server::sendToAll(const shared_ptr<Command> &command) {

View file

@ -48,10 +48,13 @@ public:
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 std::string &, const ByteArray &)> &fn);
// END Callbacks
private:
boost::asio::io_service _service;
std::unique_ptr<boost::asio::ip::tcp::acceptor> _acceptor;
@ -59,10 +62,13 @@ private:
ServerClients _clients;
// Callbacks
std::function<void(const std::string &)> _onClientConnected;
std::function<void(const std::string &)> _onClientDisconnected;
std::function<void(const std::string &, const ByteArray &)> _onCommandReceived;
// END Callbacks
Server(const Server &) = delete;
Server &operator=(const Server &) = delete;