Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/WolfireGames/overgrowth.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Online/online.cpp')
-rw-r--r--Source/Online/online.cpp1594
1 files changed, 1594 insertions, 0 deletions
diff --git a/Source/Online/online.cpp b/Source/Online/online.cpp
new file mode 100644
index 00000000..0a9d1eb3
--- /dev/null
+++ b/Source/Online/online.cpp
@@ -0,0 +1,1594 @@
+//-----------------------------------------------------------------------------
+// Name: online.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online.h"
+
+#include <Game/level.h>
+#include <Game/connection_closed_reason.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Steam/steamworks.h>
+#include <Steam/steamworks_util.h>
+
+#include <Objects/object.h>
+#include <Objects/envobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/envobjectattach.h>
+#include <Objects/riggedobject.h>
+#include <Objects/cameraobject.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Online/online_file_transfer_handler.h>
+#include <Online/online_utility.h>
+#include <Online/package_header.h>
+
+#include <Compat/compat.h>
+#include <Compat/time.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Main/scenegraph.h>
+#include <Logging/logdata.h>
+
+#if ENABLE_STEAMWORKS
+#include <isteamnetworkingutils.h>
+#include <isteamfriends.h>
+#endif
+
+#include <zstd.h>
+#include <zstd-1.5.0/lib/common/zstd_common.c>
+
+#include <limits>
+
+extern Timer game_timer;
+
+OnlineMessageHandler message_handler;
+
+OnlineMessageRef::OnlineMessageRef() : message_id(std::numeric_limits<OnlineMessageID>::max()) {
+}
+
+OnlineMessageRef::OnlineMessageRef(const OnlineMessageID message_id) : message_id(message_id) {
+ message_handler.Acquire(message_id);
+}
+
+OnlineMessageRef::OnlineMessageRef(const OnlineMessageRef& rhs) {
+ //Debug the fact that this RHS is corrupt sometimes when loading in debug.
+ message_id = rhs.message_id;
+ message_handler.Acquire(message_id);
+}
+
+OnlineMessageRef::~OnlineMessageRef() {
+ if(IsValid()) {
+ message_handler.Release(message_id);
+ }
+}
+
+OnlineMessageRef& OnlineMessageRef::operator=(const OnlineMessageRef& rhs){
+ if(IsValid()) {
+ message_handler.Release(message_id);
+ }
+
+ message_id = rhs.message_id;
+
+ message_handler.Acquire(message_id);
+ return *this;
+}
+
+OnlineMessageID OnlineMessageRef::GetID() const {
+ return this->message_id;
+}
+
+void* OnlineMessageRef::GetData() const {
+ return message_handler.GetMessageData(*this);
+}
+
+bool OnlineMessageRef::IsValid() const {
+ return message_id != std::numeric_limits<OnlineMessageID>::max();
+}
+
+void Online::GetConnectionStatus(const Peer& peer, ConnectionStatus* status) const {
+ return net.GetConnectionStatus(peer.conn_id, status);
+}
+
+void Online::RegisterHostObjectID(CommonObjectID hostid, ObjectID clientid) {
+ to_object_id[hostid] = clientid;
+ to_common_id[clientid] = hostid;
+}
+
+void Online::DeRegisterHostClientIDTranslation(ObjectID clientid) {
+ // check if we need to remove a translation
+ auto translation = GetOriginalID(clientid);
+
+ if (translation) {
+ to_object_id.erase(translation);
+ to_common_id.erase(clientid);
+ }
+}
+
+ObjectID Online::GetObjectID(CommonObjectID hostid) const {
+ auto it = to_object_id.find(hostid);
+ if (it != to_object_id.end()) {
+ return it->second;
+ } else {
+ return hostid;
+ }
+}
+
+CommonObjectID Online::GetOriginalID(ObjectID clientid) const {
+ auto it = to_common_id.find(clientid);
+ if (it != to_common_id.end()) {
+ return it->second;
+ } else {
+ return clientid;
+ }
+}
+
+void Online::ClearIDTranslations() {
+ to_object_id.clear();
+ to_common_id.clear();
+}
+
+bool Online::SendingRemovePackages() const {
+ return !loading;
+}
+
+void Online::RemoveObject(Object * o, ObjectID my_id) {
+ if (NetworkRemoveableType(o) && SendingRemovePackages()) {
+ Send<OnlineMessages::RemoveObject>(my_id, o->GetType());
+ }
+}
+
+bool Online::NetworkRemoveableType(Object * o) const {
+ EntityType type = o->GetType();
+ return type == _env_object || type == _movement_object || type == _item_object;
+}
+
+void Online::ChangeLevel(const string & id) {
+ campaign_id = ModLoading::Instance().WhichCampaignLevelBelongsTo(id);
+ host_started_level = false;
+ level_name = id;
+
+ RemoveAllAvatarIds();
+ loading = true;
+
+ online_session->outgoing_messages_lock.lock();
+ // Clear out Level persistent messages
+ for (int i = online_session->persistent_outgoing_messages.size() - 1; i >= 0; i--) {
+ OnlineMessageBase* message_base = (OnlineMessageBase*) online_session->persistent_outgoing_messages[i].GetData();
+ if (message_base->cat == OnlineMessageCategory::LEVEL_PERSISTENT) {
+ online_session->persistent_outgoing_messages.erase(online_session->persistent_outgoing_messages.begin() + i);
+ }
+ }
+ online_session->outgoing_messages_lock.unlock();
+
+ // Reset every peer to a pending connection state.
+ online_session->client_connection_manager.ResetConnections();
+
+ // Reset every player's possession
+ online_session->connected_peers_lock.lock();
+ for (auto& it : online_session->player_states) {
+ it.second.object_id = -1;
+ }
+ online_session->connected_peers_lock.unlock();
+}
+
+Online::Online() : mode(MultiplayerMode::NoMultiplayer),
+ initial_ts(0), next_available_state_id(1), next_free_peer_id(1),
+ attach_avatar_camera(true), loading(false),
+ default_hot_join_characte_path("Data/Objects/IGF_Characters/IGF_TurnerActor.xml"),
+ compression_level(10), net(this), host_started_level(false)
+{
+
+ zstdCContext = ZSTD_createCCtx();
+ zstdDContext = ZSTD_createDCtx();
+}
+
+void Online::Dispose() {
+ StopMultiplayer();
+}
+
+bool Online::IsClient() const {
+ return mode == MultiplayerMode::Client;
+}
+
+void Online::SendAvatarPaletteChange(const OGPalette & palette, ObjectID object_id) {
+ for (const auto& it : palette) {
+ Send<OnlineMessages::SetAvatarPalette>(object_id, it.color, it.channel);
+ }
+}
+
+bool Online::IsAvatarPossessed(ObjectID object_id) {
+ Object* object = Engine::Instance()->GetSceneGraph()->GetObjectFromID(object_id);
+ if (object != nullptr && object->GetType() == EntityType::_movement_object) {
+ MovementObject* mov = (MovementObject*) object;
+ return mov->controlled;
+ }
+
+ LOGW << "IsAvatarPossessed was called with invalid id: " << object_id << endl;
+ return false;
+}
+
+void Online::PossessAvatar(PlayerID player_id, ObjectID object_id) {
+ // LOGI << std::to_string(player_id) << " possessed " << std::to_string(object_id) << std::endl;
+ online_session->player_states[player_id].object_id = object_id;
+ Send<OnlineMessages::SetPlayerState>(player_id, &online_session->player_states[player_id]);
+}
+
+ObjectID Online::CreateCharacter() {
+ SceneGraph * scenegraph = Engine::Instance()->GetSceneGraph();
+
+ ObjectID id = scenegraph->map_editor->CreateObject(default_hot_join_characte_path);
+ std::string team = "";
+ vec3 position = vec3(0.0f);
+
+ // Attempt to move character close to an existing one.
+ vector<ObjectID> local_avatar_ids = GetLocalAvatarIDs();
+ if(local_avatar_ids.size() > 0) {
+ MovementObject* mymo = static_cast<MovementObject*>(scenegraph->GetObjectFromID(local_avatar_ids[0]));
+ team = mymo->GetScriptParams()->GetStringVal("Teams");
+ position = mymo->GetTranslation();
+ } else {
+ LOGW << "Unable to find locally controlled character while creating character for new connection, using fallback parameters" << std::endl;
+ }
+
+ MovementObject* mo = static_cast<MovementObject*>(scenegraph->GetObjectFromID(id));
+ mo->SetTranslation(position);
+ mo->is_player = true;
+ mo->created_on_the_fly = true;
+
+ ScriptParams * sp = mo->GetScriptParams();
+ sp->ASSetString("Teams", team);
+
+ Send<OnlineMessages::CreateEntity>(default_hot_join_characte_path, position, id);
+
+ // Set player color for new character
+ OGPalette* palettes = mo->GetPalette();
+ for (auto& palette : *palettes) {
+ if (palette.label == "Cloth") {
+ palette.color = vec3(1.0f, 0.0f, 0.0f);
+ }
+ }
+ mo->ApplyPalette(*palettes);
+ return id;
+}
+
+void Online::SetNoDataInterpStepOverRide(float override) {
+ no_data_interpstep_override = override;
+}
+
+float Online::GetNoDataInterpStepOverRide() const {
+ return no_data_interpstep_override;
+}
+
+//Client side sending camera info to host
+void Online::SendPlayerCameraState() {
+ Camera* camera = ActiveCameras::Instance()->GetCamera(0);
+ Send<OnlineMessages::CameraTransformMessage>(camera->GetPos(), camera->GetFlatFacing(), camera->GetFacing(), camera->GetUpVector(), camera->GetXRotation(), camera->GetYRotation());
+}
+
+void Online::SendPlayerInputState() {
+ //Assume player one control. For split screen multiplayer we have to start dealing with multiplayer controllers here.
+ PlayerInput* player_input = Input::Instance()->GetController(0);
+
+ for(string binding : Input::Instance()->GetAllAvailableBindings()) {
+
+ // TODO Can we make it so these bindings don't even show up in available bindings?
+ if(binding == "look_left" || binding == "look_right" || binding == "look_up" || binding == "look_down") {
+ continue;
+ }
+
+ KeyState local_keystate = player_input->key_down[binding];
+ KeyState remote_keystate = client_key_down_map_state[binding];
+
+ // if we have something that the remote does not have, update
+ if(local_keystate != remote_keystate) {
+ Send<OnlineMessages::PlayerInputMessage>(local_keystate.depth, local_keystate.depth_count, local_keystate.count, online_session->binding_id_map_[binding]);
+ client_key_down_map_state[binding] = local_keystate;
+ }
+ }
+}
+
+void Online::LateUpdate(SceneGraph * scenegraph) {
+ if (IsActive()) {
+ //Send a ping every second.
+ if(online_session != nullptr && game_timer.wall_time > online_session->last_ping_time + 1.0f) {
+ online_session->last_ping_id = online_session->ping_counter++;
+ online_session->last_ping_time = game_timer.wall_time;
+ Send<OnlineMessages::Ping>(online_session->last_ping_id);
+ }
+
+ online_session->client_connection_manager.Update();
+
+ if (IsHosting()) {
+ SyncHostSessionFlags();
+ }
+ }
+}
+
+void Online::SyncHostSessionFlags() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ for(auto& conn : online_session->connected_peers) {
+ for(auto& flag : online_session->host_session_flags) {
+ if(conn.host_session_flags.find(flag.first) == conn.host_session_flags.end() || conn.host_session_flags.find(flag.first)->second != flag.second) {
+ SendTo<OnlineMessages::HostSessionFlag>(conn.conn_id, flag.first, flag.second);
+ conn.host_session_flags[flag.first] = flag.second;
+ }
+ }
+ }
+}
+
+void Online::GenerateStateForNewConnection(NetConnectionID conn) {
+ SceneGraph* graph = Engine::Instance()->GetSceneGraph();
+ if (graph != nullptr) {
+ GenerateMovementSyncPackages(conn, graph);
+ GenerateEnvObjectSyncPackages(conn, graph);
+ }
+}
+
+void Online::GenerateEnvObjectSyncPackages(NetConnectionID conn, SceneGraph* graph) {
+ std::vector<Object*> envobjects = graph->GetObjectsOfType(EntityType::_env_object);
+
+ for (const auto& it : envobjects) {
+ EnvObject* eo = static_cast<EnvObject*>(it);
+ SendTo<OnlineMessages::EnvObjectUpdate>(conn, eo);
+ }
+}
+
+void Online::GenerateMovementSyncPackages(NetConnectionID conn, SceneGraph * graph) {
+ std::vector<Object*> movobjects = graph->GetObjectsOfType(EntityType::_movement_object);
+
+ for (const auto& it : movobjects) {
+ MovementObject * mov = static_cast<MovementObject*>(it);
+ ObjectID avatarid = mov->GetID();
+
+ // riggedbody blood?
+ std::vector<MorphTargetStateStorage> network_morphs = mov->rigged_object()->network_morphs;
+
+ for (const auto& it : network_morphs) {
+ SendTo<OnlineMessages::MorphTargetUpdate>(conn, avatarid, it);
+ }
+ }
+}
+
+bool Online::CheckLoadedMapAndCampaignState(const string & campagin, const string & level_name) {
+ Engine * engine = Engine::Instance();
+ SceneGraph * graph = engine->GetSceneGraph();
+
+ ScriptableCampaign * loaded_campagin = engine->GetCurrentCampaign();
+ if (loaded_campagin == nullptr || graph == nullptr) {
+ return false;
+ }
+
+ string current_campaign = loaded_campagin->GetCampaignID();
+ string campaignId = engine->GetCurrentCampaign()->GetCampaignID();
+ string current_level = graph->level_path_.GetOriginalPathStr();
+ return (current_campaign == campagin) && (level_name == current_level);
+}
+
+void Online::SetLevelLoaded() {
+ if (IsClient()) {
+ // We notify the host about having loaded, the host can only finish loading if we have.
+ // From the client's perspective! The host might already be ingame, client doesn't know.
+ Send<OnlineMessages::PCSLoadingCompletedMessage>();
+ }
+
+ // Host finished loading
+ if (IsHosting()) {
+ loading = false;
+ RemoveAllAvatarIds();
+ AddFreeAvatarsIds();
+ ObjectID avatar = GetFreeAvatarID();
+
+ campaign_id = ModLoading::Instance().WhichCampaignLevelBelongsTo(level_name);
+ LOG_ASSERT(level_name == Engine::Instance()->GetSceneGraph()->level_path_.GetOriginalPathStr());
+ }
+}
+
+uint32_t Online::IncomingMessageCount() const {
+ if(IsActive()) {
+ return online_session->incoming_messages.size();
+ }
+ return 0;
+}
+
+uint32_t Online::OutgoingMessageCount() const {
+ if(IsActive()) {
+ return online_session->outgoing_messages.size();
+ }
+ return 0;
+}
+
+bool Online::GetIfPendingAngelScriptUpdates() {
+ return online_session->peer_queued_level_updates.empty() == false;
+}
+
+bool Online::GetIfPendingAngelScriptStates() {
+ return online_session->peer_queued_sync_updates.empty() == false;
+}
+
+AngelScriptUpdate Online::GetAngelScriptUpdate() {
+ if (online_session->peer_queued_level_updates.empty() == false) {
+ return online_session->peer_queued_level_updates.front();
+ }
+ return AngelScriptUpdate();
+}
+
+void Online::MoveAngelScriptQueueForward() {
+ online_session->peer_queued_level_updates.pop();
+}
+
+void Online::MoveAngelStateQueueForward() {
+ online_session->peer_queued_sync_updates.pop();
+}
+
+AngelScriptUpdate Online::GetAngelScriptStates() {
+ if (online_session->peer_queued_sync_updates.empty() == false) {
+ return online_session->peer_queued_sync_updates.front();
+ }
+ return AngelScriptUpdate();
+}
+
+uint32_t Online::AddSyncState(uint32_t state, const vector<char>& data) {
+ AngelScriptUpdate temp;
+
+ temp.state = state;
+ temp.data = data;
+
+ lock_guard<mutex> guard(online_session->state_packages_lock);
+ online_session->state_packages[state] = temp;
+
+ return state;
+}
+
+uint32_t Online::RegisterMPState(const string& state) {
+ if (states.find(state) != states.end()) {
+ return states[state];
+ }
+
+ states[state] = next_available_state_id++;
+ return states[state];
+}
+
+void Online::SetTickperiod(const uint32_t & tickperiod) {
+ tick_period = tickperiod;
+}
+
+bool Online::IsActive() const {
+ return (IsHosting() || IsClient()) && online_session != nullptr;
+}
+
+bool Online::IsHosting() const {
+ return mode == MultiplayerMode::Host;
+}
+
+bool Online::IsAwaitingShutdown() const {
+ return mode == MultiplayerMode::AwaitingShutdown;
+}
+
+void Online::StartMultiplayer(MultiplayerMode multiplayerMode) {
+ //for(string binding : Input::Instance()->GetAllAvailableBindings()) {
+ // LOGI << binding << " " << (int)Input::Instance()->GetBindID(binding) << endl;
+ //}
+
+ //for(string binding_cat : Input::Instance()->GetAvailableBindingCategories()) {
+ // LOGI << binding_cat << endl;
+ //}
+
+ if(online_session != nullptr) {
+ LOGE << "Attempting to start multiplayer while the previous session is still valid!" << endl;
+ // TODO can we close the application here with a big error? This should be a fatal issue!
+ }
+
+ if(OnlineUtility::HasActiveIncompatibleMods()) {
+ LOGE << "Online multiplayer was enabled despite using unsupported mods, this might cause issues!" << std::endl;
+ LOGE << "Incompatible mods: " << OnlineUtility::GetActiveIncompatibleModsString() << std::endl;
+ }
+
+ online_session = new OnlineSession();
+
+ host_started_level = false;
+ online_session->host_session_flags[OnlineFlags::ALLOWSEDITOR] = false;
+
+ mode = multiplayerMode;
+
+ Engine * engine = Engine::Instance();
+ SceneGraph * graph = engine->GetSceneGraph();
+
+ string mpcachedir = string(write_path) + string("multiplayercache/");
+
+ CreateParentDirs(mpcachedir);
+
+ AddPath(mpcachedir.c_str(), PathFlags::kDataPaths);
+
+ if (mode == MultiplayerMode::Host) {
+ // assert(scenegraph);
+ // TODO this might never be true due to how we handle starting multiplayer now
+ if (engine->GetSceneGraph() != nullptr) {
+ RemoveAllAvatarIds();
+ AddFreeAvatarsIds();
+ host_started_level = true;
+
+ // index 0 is probably not correct
+ ScriptableCampaign* sc = engine->GetCurrentCampaign();
+ if (sc != nullptr) {
+ campaign_id = sc->GetCampaignID();
+ } else {
+ campaign_id = -1;
+ }
+
+ // you can check with scenegraph
+ level_name = graph->level_path_.GetOriginalPathStr();
+ }
+
+ //Assign mappings for input bindings.
+ for(string binding : Input::Instance()->GetAllAvailableBindings()) {
+ AssignBindID(binding);
+ }
+
+ online_session->local_player_id = -1;
+ online_session->player_states[online_session->local_player_id] = GenerateHostPlayerState();
+ }
+
+ net.Initialize();
+
+ engine->menu_paused = false;
+ engine->CommitPause();
+
+ tick_period = 30;
+ socket_thread = (thread(&Online::NetworkDataThread, this));
+}
+
+void Online::StartHostingMultiplayer() {
+ if(!IsActive()) {
+ StartMultiplayer(MultiplayerMode::Host);
+ CreateListenSocketIP("");
+ }
+}
+
+void Online::PerformLevelChangeCleanUp() {
+ lock_guard<mutex> guard(online_session->state_packages_lock);
+ online_session->state_packages.clear();
+}
+
+const vector<Peer> Online::GetPeers() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ return online_session->connected_peers;
+}
+
+Peer* Online::GetPeerFromConnection(NetConnectionID conn_id) {
+ for(int i = 0; i < online_session->connected_peers.size(); i++) {
+ if(online_session->connected_peers[i].conn_id == conn_id) {
+ return &online_session->connected_peers[i];
+ }
+ }
+ return nullptr;
+}
+
+Peer* Online::GetPeerFromID(PeerID peer_id) {
+ for(int i = 0; i < online_session->connected_peers.size(); i++) {
+ if(online_session->connected_peers[i].peer_id == peer_id) {
+ return &online_session->connected_peers[i];
+ }
+ }
+ return nullptr;
+}
+
+vector<Peer>::iterator Online::GetPeerIt(NetConnectionID conn_id) {
+ for(int i = 0; i < online_session->connected_peers.size(); i++) {
+ if(online_session->connected_peers[i].conn_id == conn_id) {
+ return online_session->connected_peers.begin() + i;
+ }
+ }
+ return online_session->connected_peers.end();
+}
+
+void Online::QueueStopMultiplayer() {
+ if(IsActive()) {
+ mode = MultiplayerMode::AwaitingShutdown;
+ }
+}
+
+void Online::StopMultiplayer() {
+ if(IsActive() || mode == MultiplayerMode::AwaitingShutdown) {
+ mode = MultiplayerMode::CleanUp;
+
+ { // Immediately disconnect all clients
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ for (auto& it : online_session->connected_peers) {
+ // We kill the connection on the socket directly, we no longer want to keep track of any connection changes
+ // We can't call CloseConnectionImmediate here, as it modifies the array we are iterating over and uses the same lock
+ net.CloseConnection(it.conn_id, ConnectionClosedReason::HOST_STOPPED_HOSTING);
+ }
+ online_session->connected_peers.clear(); // We shouldn't have to clear this, since we plan on getting rid of online_session shortyly
+ StopListening();
+ }
+
+ // Clear up on the fly
+ SceneGraph* graph = Engine::Instance()->GetSceneGraph();
+ if (graph != nullptr) {
+ vector<Object *> objects = graph->GetObjectsOfType(EntityType::_movement_object);
+
+ for (auto it : objects) {
+ if (it->created_on_the_fly) {
+ graph->map_editor->RemoveObject(it, graph, true);
+ DeRegisterHostClientIDTranslation(it->GetID());
+ }
+ }
+ }
+
+ next_available_state_id = 1;
+ next_free_peer_id = 1;
+
+ host_started_level = false;
+ loading = false;
+ p2p_sockets_active = false;
+
+ if (socket_thread.joinable()) {
+ socket_thread.join();
+ }
+
+ // Make sure we get booted into the main menu, if we aren't there already.
+ if(Engine::Instance()->current_engine_state_.type != EngineStateType::kEngineScriptableUIState) {
+ Engine::Instance()->ScriptableUICallback("back_to_main_menu");
+ }
+
+ mode = MultiplayerMode::NoMultiplayer;
+
+ delete online_session;
+ online_session = nullptr;
+ }
+}
+
+bool Online::IsLocalAvatar(const ObjectID avatar_id) const {
+ if (online_session == nullptr) {
+ // Everything is local when playing offline
+ return true;
+ }
+
+ // Is this avatar controlled by a playerstate? If yes, is it us?
+ for (const auto& it : online_session->player_states) {
+ if (it.second.object_id == avatar_id) {
+ return it.first == online_session->local_player_id;
+ }
+ }
+
+ // As a host, everything not convered in a playerstate is local
+ // As a client, everything not controlled by us playerstate is remote
+ return IsHosting();
+}
+
+vector<ObjectID> Online::GetLocalAvatarIDs() const {
+ vector<ObjectID> local_avatar_ids;
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+ if(sg != nullptr) {
+ for (const auto& it : sg->GetControllableMovementObjects()) {
+ if (IsLocalAvatar(it->GetID())) {
+ local_avatar_ids.push_back(it->GetID());
+ }
+ }
+ }
+ return local_avatar_ids;
+}
+
+PlayerState Online::GenerateHostPlayerState() const {
+ if(!IsHosting()) {
+ LOGW << "Called GenerateHostPlayerState(), but wasn't hosting. Only the host should generate playerstates, especially his own!" << std::endl;
+ PlayerState player_state;
+ return player_state;
+ }
+
+ PlayerState host_player_state;
+ host_player_state.ping = 0;
+ host_player_state.playername = OnlineUtility::GetPlayerName();
+ host_player_state.object_id = -1;
+ return host_player_state;
+}
+
+void Online::AddLocalChatMessage(string chat_message) {
+ lock_guard<mutex> guard(online_session->chat_messages_lock);
+ online_session->chat_messages.push_back({game_timer.game_time, chat_message});
+}
+
+void Online::RemoveOldChatMessages(float threshold) {
+ lock_guard<mutex> guard(online_session->chat_messages_lock);
+ online_session->chat_messages.erase(remove_if(online_session->chat_messages.begin(), online_session->chat_messages.end(),
+ [threshold](ChatMessage i) { return game_timer.game_time - i.time > threshold; }), online_session->chat_messages.end());
+ }
+
+const vector<ChatMessage>& Online::GetChatMessages() const {
+ return online_session->chat_messages;
+}
+
+void Online::AddPeer(NetFrameworkConnectionStatusChanged *data) {
+ if (IsHosting()) {
+ if (IsLobbyFull()) {
+ CloseConnection(data->conn_id, ConnectionClosedReason::LOBBY_FULL);
+ AddLocalChatMessage(string("Somebody tried connecting but we don't have any slots free"));
+ } else {
+ Peer peer;
+ peer.conn_id = data->conn_id;
+ peer.peer_id = next_free_peer_id++;
+ peer.m_info = data->conn_info;
+ {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ online_session->connected_peers.push_back(peer);
+ online_session->client_connection_manager.AddConnection(peer.peer_id, data->conn_id);
+ }
+ }
+ } else {
+ Peer peer;
+ peer.conn_id = data->conn_id;
+ peer.peer_id = 0;
+ peer.m_info = data->conn_info;
+ {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ online_session->connected_peers.push_back(peer);
+ }
+ }
+}
+
+/// Queue termination of a connection
+void Online::CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason) {
+ if(online_session != nullptr) {
+ ConnectionToBeClosed temp;
+ temp.conn_id = conn_id;
+ temp.reason = reason;
+
+ online_session->connections_waiting_to_be_closed.push(temp);
+ } else {
+ LOGW << "Attempted to close a connection without a valid online session" << endl;
+ assert(online_session != nullptr); // Kill the application for now, this should not be happening!
+ }
+}
+
+/// Close a connection right away, possibly unsafe to call from anywhere
+void Online::CloseConnectionImmediate(NetConnectionID conn_id, ConnectionClosedReason reason) {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+
+ net.CloseConnection(conn_id,reason);
+
+ vector<Peer>::iterator peer = GetPeerIt(conn_id);
+ if (peer != online_session->connected_peers.end()) {
+ PlayerID player_id = peer->peer_id; // TODO We assume peer_id == player_id
+ if (online_session->player_states.find(player_id) != online_session->player_states.end()) {
+ auto& player_state = online_session->player_states[player_id];
+
+ SendRawChatMessage(player_state.playername + " disconnected!");
+
+ // Clean up objects assigned to disconnected player
+ SceneGraph * graph = Engine::Instance()->GetSceneGraph();
+ if (graph != nullptr) {
+ Object * o = graph->GetObjectFromID(player_state.object_id);
+ if (o != nullptr) {
+ if (o->created_on_the_fly) {
+ graph->map_editor->RemoveObject(o, graph);
+ } else {
+ online_session->free_avatars.push_back(player_state.object_id);
+ }
+ ((MovementObject*)o)->remote = false;
+ }
+ }
+
+ // Remove playerstate
+ online_session->player_states.erase(player_id);
+ Send<OnlineMessages::RemovePlayerState>(player_id);
+
+ // TODO do we need to clean the camera and controller of the player?
+ }
+
+ online_session->client_connection_manager.RemoveConnection(peer->peer_id);
+ online_session->connected_peers.erase(peer);
+ } else {
+ LOGW << "Tried closing a connection, but there is no peer assigned to NetConnectionID: " << conn_id << std::endl;
+ }
+}
+
+void Online::CheckPendingMessages() {
+ //Locking online_session->connected_peers_lock here because it's used in some arrays here, and if done more specifically
+ //theres a risk for overlapping locks with CheckNewMessages(), causing a full lockup.
+ //TODO: Reduce number of locks in multiplayer.
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+
+ {
+ lock_guard<mutex> guard(online_session->incoming_messages_lock);
+
+ for(auto& it : online_session->incoming_messages) {
+ message_handler.Execute(it.message, it.from);
+ }
+
+ online_session->incoming_messages.clear();
+ }
+
+ if (host_started_level) {
+ ApplyPlayerInput();
+ }
+}
+
+void Online::ApplyPlayerInput() {
+ for(auto& it : online_session->player_inputs) {
+ // We don't want to apply two inputs of the same key on the same frame
+ // Releasing and pressing down the key should not be processed on the same frame
+ unordered_map<uint8_t, int> depth;
+
+ for (list<OnlineMessageRef>::iterator i = it.second.begin(); i != it.second.end();) {
+ PlayerInputMessage* piks = static_cast<PlayerInputMessage*>(it.second.front().GetData());
+
+ // Get player involved in the input, discard input if missing
+ if (online_session->player_states.find(it.first) == online_session->player_states.end()) {
+ break;
+ }
+
+ int controller_id = online_session->player_states[it.first].controller_id;
+ PlayerInput* player_input = Input::Instance()->GetController(controller_id);
+ if (player_input == nullptr) {
+ break;
+ }
+
+ if (depth.find(piks->id) != depth.end()) {
+ int velocity = piks->count - depth[piks->id];
+
+ // a negative velocity mean't that we pressed and release
+ // process this later.
+ if (velocity < 0) {
+ i++;
+ continue;
+ }
+
+ // this is a first frame down, process all other inputs on the next frame
+ if (depth[piks->id] == 1) {
+ i++;
+ continue;
+ }
+ }
+
+ // process the input
+ KeyState& key_state = player_input->key_down[online_session->binding_name_map_[piks->id]];
+ depth[piks->id] = piks->count;
+ key_state.count = piks->count;
+ key_state.depth_count = piks->depth_count;
+ key_state.depth = piks->depth;
+
+ // we are done with this input
+ it.second.erase(i++);
+ }
+ }
+}
+
+PlayerState Online::GetOwnPlayerState() {
+ std::lock_guard<std::mutex> guard (online_session->connected_peers_lock);
+
+ auto it = online_session->player_states.find(online_session->local_player_id);
+ if (it != online_session->player_states.end()) {
+ return it->second;
+ }
+
+ LOGW << "Tried to get own players state, but there is no state registered for player id: " << std::to_string(online_session->local_player_id) << std::endl;
+ PlayerState fallback;
+ return fallback;
+}
+
+bool Online::TryGetPlayerState(PlayerState& player_state, ObjectID object_id) const {
+ if (IsActive()) {
+ for (auto& it : online_session->player_states) {
+ if (it.second.object_id == object_id) {
+ player_state = it.second;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void Online::AssignNewControllerForPlayer(PlayerID player_id) {
+ online_session->player_states[player_id].controller_id = Input::Instance()->AllocateRemotePlayerInput();
+ online_session->player_states[player_id].camera_id = ActiveCameras::Instance()->CreateVirtualCameraInstance();
+}
+
+size_t Online::CompressData(vector<char>& target_buffer, const void* source_buffer, uint32_t source_size) {
+ size_t maxSize = ZSTD_compressBound(source_size);
+
+ if(target_buffer.size() < maxSize) {
+ target_buffer.resize(maxSize);
+ }
+
+ size_t compressedSize = ZSTD_compressCCtx(zstdCContext,
+ target_buffer.data(),
+ target_buffer.size(),
+ source_buffer,
+ source_size,
+ compression_level);
+
+ if(compressedSize > maxSize) {
+ LOGE << "Failed to compress data, assumed size was smaller than the real size" << endl;
+ }
+
+ target_buffer.resize(compressedSize);
+
+ return compressedSize;
+}
+
+size_t Online::DecompressData(vector<char>& target_buffer, const void* source_buffer, size_t source_size) {
+ size_t decompressed_size = ZSTD_getFrameContentSize(source_buffer, source_size);
+
+ if(decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) {
+ LOGW << "Unable to determine uncompressed package size" << std::endl;
+ } else if (decompressed_size == ZSTD_CONTENTSIZE_ERROR) {
+ LOGE << "Error while trying to determine content size" << std::endl;
+ } else if(target_buffer.size() < decompressed_size) {
+ target_buffer.resize(decompressed_size);
+ }
+
+ size_t final_decompressed_size = 0;
+ while(true) {
+ size_t final_decompressed_size = ZSTD_decompressDCtx(zstdDContext, target_buffer.data(), target_buffer.size(), source_buffer, source_size);
+ if (ZSTD_isError(final_decompressed_size)) {
+ LOGE << ZSTD_getErrorString(ZSTD_getErrorCode(final_decompressed_size)) << endl;
+ } else if(final_decompressed_size > target_buffer.size()) {
+ //If the final decompressed size is larger than the target buffer, re-do the decompression after resizing.
+ target_buffer.resize(final_decompressed_size);
+ continue;
+ }
+ break;
+ }
+
+ return final_decompressed_size;
+}
+
+uint32_t Online::GetPlayerCount() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ return online_session->player_states.size();
+}
+
+/// Hosts will add their name here, clients will have their name added by the host later
+void Online::BroadcastChatMessage(const string& chat_message) {
+ if(IsActive()) {
+ if(IsHosting()) {
+ if (OnlineUtility::IsCommand(chat_message)) {
+ OnlineUtility::HandleCommand(online_session->local_player_id, chat_message);
+ AddLocalChatMessage(OnlineUtility::GetPlayerName() + ": " + chat_message);
+ } else {
+ SendRawChatMessage(OnlineUtility::GetPlayerName() + ": " + chat_message);
+ }
+ } else {
+ SendRawChatMessage(chat_message);
+ }
+ }
+}
+
+void Online::SendRawChatMessage(const string& raw_chat_entry) {
+ if(IsActive()) {
+ Send<OnlineMessages::ChatEntryMessage>(raw_chat_entry);
+ if (IsHosting()) {
+ AddLocalChatMessage(raw_chat_entry);
+ }
+ }
+}
+
+vector<PlayerState> Online::GetPlayerStates() {
+ if (IsActive()) {
+ vector<PlayerState> player_states;
+ for (const auto& it : online_session->player_states) {
+ player_states.push_back(it.second);
+ }
+ return player_states;
+ }
+
+ vector<PlayerState> empty;
+ return empty;
+}
+
+void Online::Send(const OnlineMessageRef& message_ref) {
+ if(IsActive()) {
+ lock_guard<mutex> guard(online_session->outgoing_messages_lock);
+ online_session->outgoing_messages.push_back({true, 0, message_ref});
+ }
+}
+
+void Online::SendTo(NetConnectionID target, const OnlineMessageRef& message_ref) {
+ if(IsActive()) {
+ lock_guard<mutex> guard(online_session->outgoing_messages_lock);
+ online_session->outgoing_messages.push_back({false, target, message_ref});
+ }
+}
+
+void Online::SendIntFloatScriptParam(uint32_t id, const string& key, const ScriptParam& param) {
+ if (param.IsFloat()) {
+ Send<OnlineMessages::SPUnionMessage>(id, key, param.GetFloat(), ScriptParam::ScriptParamType::FLOAT, param.editor().type(), param.editor().GetDetails());
+ } else {
+ Send<OnlineMessages::SPUnionMessage>(id, key, param.GetInt(), ScriptParam::ScriptParamType::INT, param.editor().type(), param.editor().GetDetails());
+ }
+}
+
+void Online::SendStringScriptParam(uint32_t id, const string& key, const ScriptParam& param) {
+ Send<OnlineMessages::SPStringMessage>(id, key, param.GetStringForSocket(), param.editor().type(), param.editor().GetDetails());
+}
+
+void Online::SendScriptParam(uint32_t id, const string& key, const ScriptParam& param) {
+ if (param.IsString()) {
+ SendStringScriptParam(id, key, param);
+ } else {
+ SendIntFloatScriptParam(id, key, param);
+ }
+}
+
+void Online::SendScriptParamMap(uint32_t id, const ScriptParamMap& param){
+ for (auto& it : param) {
+ SendScriptParam(id, it.first, it.second);
+ }
+}
+
+bool Online::GetHostAllowsClientEditor() const {
+ if (online_session == nullptr)
+ return false;
+ return online_session->host_session_flags[OnlineFlags::ALLOWSEDITOR];
+}
+
+void Online::SetIfHostAllowsClientEditor(bool mode) {
+ if (online_session != nullptr) {
+ online_session->host_session_flags[OnlineFlags::ALLOWSEDITOR] = mode;
+ }
+}
+
+void Online::SetDefaultHotJoinCharacter(const string & path) {
+ default_hot_join_characte_path = path;
+}
+
+string Online::GetDefaultHotJoinCharacter() const {
+ return default_hot_join_characte_path;
+}
+
+void Online::SessionStarted(bool host_started_the_level) {
+ host_started_level = host_started_the_level;
+}
+
+bool Online::ForceMapStartOnLoad() const {
+ return host_started_level;
+}
+
+bool Online::AllClientsReady() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ return online_session->client_connection_manager.IsEveryClientLoaded();
+}
+
+ScriptParams* Online::GetScriptParamsFromID(ObjectID id) {
+ if(Engine::Instance()->GetSceneGraph() != nullptr) {
+ if(id == numeric_limits<uint32_t>::max())
+ return &Engine::Instance()->GetSceneGraph()->level->script_params();
+
+ Object* o = Engine::Instance()->GetSceneGraph()->GetObjectFromID(id);
+ if (o != nullptr)
+ return o->GetScriptParams();
+ }
+ return nullptr;
+}
+
+void Online::UpdateMovementObjectFromID(uint32_t id) {
+ if(id != numeric_limits<uint32_t>::max()) {
+ Object * o = Engine::Instance()->GetSceneGraph()->GetObjectFromID(id);
+ if(o != nullptr && o->GetType() == EntityType::_movement_object) {
+ ((MovementObject*)o)->UpdateScriptParams();
+ }
+ }
+}
+
+bool Online::SetAvatarCameraAttachedMode(bool mode) {
+ return attach_avatar_camera = mode;
+}
+
+bool Online::IsAvatarCameraAttached() const {
+ return attach_avatar_camera && Engine::Instance()->GetSceneGraph()->map_editor->state_ == MapEditor::kInGame;
+}
+
+void Online::SendLevelMessage(const string& msg) {
+ // we currently only want start_dialogue
+ if (msg.find("reset") == string::npos)
+ return; // tutorial is very spammy
+
+ Send<OnlineMessages::SendLevelMessage>(msg);
+}
+
+void Online::UpdateObjects(SceneGraph *scenegraph_) {
+ uint32_t wall_ticks = game_timer.GetWallTicks();
+ uint32_t tick_delta = wall_ticks - last_update_wall_ticks;
+ uint32_t quarter_tick_delta = wall_ticks - last_quarter_update_wall_ticks;
+
+ bool is_quarter_tick = quarter_tick_delta >= tick_period/4;
+ bool is_tick = tick_delta >= tick_period;
+
+ if (IsHosting()) {
+ for(Object *o : scenegraph_->movement_objects_) {
+ MovementObject* mo = static_cast<MovementObject*>(o);
+ RiggedObject* rigged_object = mo->rigged_object();
+
+ // TODO: Loop over peers and do a more frequent update of their own character.
+ // Increse the rate further whenever there's a recent input package from that peer tied to the character.
+ // We want to send a package the same frame that the object's movement intention has changed to keep it up-to-date.
+ // We check when the movementobject was last updated. There is no reason to send updates if it hasn't updated
+ static std::unordered_map<MovementObject*, float> last_timestamps;
+ if (is_quarter_tick && last_timestamps[mo] != mo->walltime_last_update) {
+ last_timestamps[mo] = mo->walltime_last_update;
+ Send<OnlineMessages::MovementObjectUpdate>(mo);
+
+ vector<MorphTargetStateStorage>& network_morphs = rigged_object->network_morphs;
+ for (auto& it : network_morphs) {
+ if (it.dirty) {
+ Send<OnlineMessages::MorphTargetUpdate>(mo->GetID(), it);
+ it.dirty = false;
+ }
+ }
+ }
+ }
+
+ if (is_tick) {
+ for (Object *o : scenegraph_->objects_) {
+ switch (o->GetType()) {
+ case _env_object:
+ if (o->online_transform_dirty) {
+ Send<OnlineMessages::EnvObjectUpdate>(static_cast<EnvObject*>(o));
+ }
+ break;
+
+ case _item_object:
+ Send<OnlineMessages::ItemUpdate>(static_cast<ItemObject*>(o));
+ break;
+ }
+ o->online_transform_dirty = false;
+ }
+ }
+ } else if(IsClient()) {
+ if (is_tick) {
+ SendPlayerCameraState();
+ }
+ //Send input information every frame, we want to make sure we get the latest.
+ SendPlayerInputState();
+ }
+
+ if(is_tick) {
+ last_update_wall_ticks += tick_delta;
+ }
+
+ if (is_quarter_tick) {
+ last_quarter_update_wall_ticks += quarter_tick_delta;
+ }
+}
+
+bool Online::HasFreeAvatars() const {
+ return !online_session->free_avatars.empty();
+}
+
+uint32_t Online::GetNumberOfFreeAvatars() const {
+ return online_session->free_avatars.size();
+}
+
+uint8_t Online::GetPlayerLimit() {
+ if(Engine::Instance()->GetSceneGraph() != nullptr && Engine::Instance()->GetSceneGraph()->level != nullptr) {
+ if(Engine::Instance()->GetSceneGraph()->level->script_params().HasParam("Player Limit")) {
+ return Engine::Instance()->GetSceneGraph()->level->script_params().ASGetInt("Player Limit");
+ }
+ return 8; // Default to 8 players (arbitrary limit)
+ }
+ return 1; // No level, no players
+}
+
+bool Online::IsLobbyFull() {
+ return GetPlayerLimit() <= GetPlayerCount();
+}
+
+void Online::AddFreeAvatarsIds() {
+ Engine::Instance()->GetAvatarIds(online_session->free_avatars);
+}
+
+void Online::RemoveAllAvatarIds() {
+ online_session->free_avatars.clear();
+ online_session->taken_avatars.clear();
+}
+
+void Online::RemoveFreeAvatarId(uint32_t id) {
+ vector<ObjectID>::iterator it = find(online_session->free_avatars.begin(), online_session->free_avatars.end(), id);
+
+ if (it != online_session->free_avatars.end()) {
+ online_session->free_avatars.erase(it);
+ }
+}
+
+void Online::AddFreeAvatarId(uint32_t id) {
+ vector<ObjectID>::iterator it = find(online_session->taken_avatars.begin(), online_session->taken_avatars.end(), id);
+
+ if (it == online_session->taken_avatars.end()) {
+ online_session->free_avatars.push_back(id);
+ }
+}
+
+ObjectID Online::GetFreeAvatarID() {
+ ObjectID avatarID = -1;
+ if (!online_session->free_avatars.empty()) {
+ auto front = online_session->free_avatars.begin();
+ avatarID = *front;
+ online_session->taken_avatars.push_back(avatarID);
+ online_session->free_avatars.erase(front);
+ }
+
+ return avatarID;
+}
+
+void Online::OnConnectionChange(NetFrameworkConnectionStatusChanged *data) {
+ switch (data->conn_info.connection_state) {
+ case NetFrameworkConnectionState::Connecting: {
+ if (IsHosting()) {
+ bool accept_connection = true;
+ if(accept_connection) {
+ int accept_connection_result;
+ bool is_ok = net.AcceptConnection(data->conn_id, &accept_connection_result);
+
+ if (is_ok) {
+ AddLocalChatMessage(string("Accepted connection: ") + to_string(data->conn_id));
+ } else {
+ string error_string = to_string(data->conn_id) + " failed to accept connection: (" + net.GetResultString(accept_connection_result) + ")";
+ AddLocalChatMessage(error_string);
+ LOGW << error_string << endl;
+ }
+ } else {
+ net.CloseConnection(data->conn_id, ConnectionClosedReason::UNSPECIFIED);
+ }
+ } else {
+ //We come here when we have an outward connection from the client.
+ }
+ break;
+ }
+
+ case NetFrameworkConnectionState::ClosedByPeer:
+ {
+ ConnectionClosedReason reason = data->conn_info.end_reason;
+ CloseConnection(data->conn_id, reason);
+ if (IsClient()) {
+ StopMultiplayer();
+
+ if(ConnectionClosedReasonUtil::IsUnusualReason(reason)) {
+ string error = ConnectionClosedReasonUtil::GetErrorMessage(reason);
+ Engine::Instance()->QueueErrorMessage("Connection Closed", error);
+ }
+ }
+ break;
+ }
+
+ case NetFrameworkConnectionState::ProblemDetectedLocally:
+ {
+ ConnectionClosedReason reason = data->conn_info.end_reason;
+ if (IsClient()) {
+ if(ConnectionClosedReasonUtil::IsUnusualReason(reason)) {
+ string error = ConnectionClosedReasonUtil::GetErrorMessage(reason);
+ Engine::Instance()->QueueErrorMessage("Connection Lost", error);
+ }
+ }
+
+ CloseConnection(data->conn_id, reason);
+ break;
+ }
+
+ case NetFrameworkConnectionState::FindingRoute: {
+ // packages will be dropped in here
+ break;
+ }
+
+ case NetFrameworkConnectionState::Connected: {
+ AddPeer(data);
+ break;
+ }
+
+ default: {
+ LOGE << "Online::OnConnectionChange: Connection has unhandeld state" << endl;
+ LOGE << data->conn_info.connection_state << endl;
+ }
+ }
+}
+
+void Online::StopListening() {
+ for (auto& socket : listen_sockets) {
+ net.CloseListenSocket(socket);
+ }
+
+ listen_sockets.clear();
+}
+
+bool Online::NetFrameworkHasFriendInviteOverlay() const {
+ return net.HasFriendInviteOverlay();
+}
+
+void Online::ActivateGameOverlayInviteDialog() {
+ if (!IsActive()) {
+ LOGW << "Attempted to show Game Overlay Invite dialogue while offline!" << std::endl;
+ return;
+ }
+
+#if ENABLE_STEAMWORKS
+ // TODO this is steam specific code, it needs to be moved to SteamNetFramework
+ // Preferably called "ActivateFriendInviteOverlay()" to match HasFriendInviteOverlay()
+ SteamworksMatchmaking *matchmaking = Steamworks::Instance()->GetMatchmaking();
+ if (matchmaking) {
+ if (!p2p_sockets_active) {
+ HSteamListenSocket listen = matchmaking->ActivateGameOverlayInviteDialog();
+ listen_sockets.insert(listen);
+ p2p_sockets_active = true;
+ } else {
+ matchmaking->OpenInviteDialog();
+ }
+ } else {
+ LOGE << "Couldn't open invite overlay page" << endl;
+ }
+#else
+ LOGW << "Tried opening a game invite overlay, but we haven't compiled in any support for one." << endl;
+#endif
+}
+
+void Online::CreateListenSocketIP(const string &local_address) {
+ NetListenSocketID listen_socket = net.CreateListenSocketIP(local_address);
+ listen_sockets.insert(listen_socket);
+}
+
+void Online::ConnectByIPAddress(const string &address) {
+ if (!IsActive()) {
+ StartMultiplayer(MultiplayerMode::Client);
+
+ NetConnectionID conn_id;
+
+ if(net.ConnectByIPAddress(address, &conn_id) == false) {
+ StopMultiplayer();
+ }
+ }
+}
+
+uint8_t Online::GetBindID(string binding_name) {
+ return online_session->binding_id_map_[binding_name];
+}
+
+void Online::AssignBindID(string binding_name) {
+ if(online_session->binding_id_map_.find(binding_name) == online_session->binding_id_map_.end()) {
+ online_session->binding_id_map_[binding_name] = ++online_session->binding_id_map_counter_;
+ online_session->binding_name_map_[online_session->binding_id_map_[binding_name]] = binding_name;
+ } else {
+ LOGW << "Binding \"" << binding_name << "\" has been assigned multiple times!" << std::endl;
+ }
+}
+
+void Online::NetworkDataThread() {
+ while (IsActive()) {
+ net.Update();
+ CheckNewMessages();
+ SendMessages();
+
+ if(IsHosting()) {
+ HandleConnectionsToBeClosed();
+ }
+
+ message_handler.StepFreeUnreferencedMessage();
+ }
+}
+
+void Online::ClearOnlineSession() {
+ delete online_session;
+ online_session = new OnlineSession();
+}
+
+void Online::SendMessages() {
+ SendMessageObjects();
+ if (IsHosting()) {
+ SendStateMessages();
+ }
+}
+
+void Online::SendMessageObjects() {
+ online_session->connected_peers_lock.lock();
+
+ online_session->outgoing_messages_lock.lock();
+
+ //First, send persistent packages, if we see there's a new client who isn't up to date.
+ if(host_started_level && IsHosting()) {
+ for(int k = 0; k < online_session->connected_peers.size(); k++) {
+ Peer& peer = online_session->connected_peers[k];
+
+ if(online_session->client_connection_manager.IsClientLoaded(peer.peer_id) && online_session->client_connection_manager.HasClientGottenPersistentQueue(peer.peer_id) == false) {
+ for(int i = 0; i < online_session->persistent_outgoing_messages.size(); i++) {
+ OnlineMessageBase* omb = static_cast<OnlineMessageBase*>(message_handler.GetMessageData(online_session->persistent_outgoing_messages[i]));
+
+ if(omb != nullptr) {
+ PackageHeader package_header;
+
+ package_header.package_type = PackageHeader::Type::MESSAGE_OBJECT;
+ package_header.message_ref = online_session->persistent_outgoing_messages[i];
+
+ binn* l = package_header.Serialize();
+
+ size_t compressed_size = CompressData(compressed_serialization_buffer, binn_ptr(l), binn_size(l));
+
+ binn_free(l);
+
+ SendMessageToConnection(peer.conn_id, compressed_serialization_buffer.data(), compressed_size, true, false);
+ } else {
+ LOGE << "Package data pointer was null, this is unexpected and odd." << endl;
+ }
+ }
+ online_session->client_connection_manager.SetClientGottenPersistentQueue(peer.peer_id);
+ }
+ }
+ }
+
+ //Send the standard message queues. Store away persistent messages for future client peers if we are host.
+ for(int i = 0; i < online_session->outgoing_messages.size(); i++) {
+ OnlineSession::OutgoingMessage& outgoing_message = online_session->outgoing_messages[i];
+ OnlineMessageBase* omb = static_cast<OnlineMessageBase*>(message_handler.GetMessageData(outgoing_message.message));
+
+ if(omb != nullptr) {
+ PackageHeader package_header;
+
+ package_header.package_type = PackageHeader::Type::MESSAGE_OBJECT;
+ package_header.message_ref = outgoing_message.message;
+
+ binn* l = package_header.Serialize();
+
+ size_t compressed_size = CompressData(compressed_serialization_buffer, binn_ptr(l), binn_size(l));
+
+ binn_free(l);
+
+ for(int k = 0; k < online_session->connected_peers.size(); k++) {
+ Peer& peer = online_session->connected_peers[k];
+ bool send_message = true;
+
+ if(outgoing_message.broadcast == false && outgoing_message.target != peer.conn_id) {
+ send_message = false;
+ //Don't send packags to client peers who have recently connected, but hasn't loaded the level, or finished getting the persistent queue yet.
+ } else if(IsHosting() && omb->cat != OnlineMessageCategory::TRANSIENT && online_session->client_connection_manager.HasClientGottenPersistentQueue(peer.peer_id) == false) {
+ send_message = false;
+ }
+
+ if(send_message) {
+ SendMessageToConnection(peer.conn_id, compressed_serialization_buffer.data(), compressed_size, omb->reliable_delivery, i+1 == online_session->outgoing_messages.size());
+ }
+ }
+
+ if(IsHosting()) {
+ if(omb->cat == OnlineMessageCategory::LEVEL_PERSISTENT) {
+ online_session->persistent_outgoing_messages.push_back(outgoing_message.message);
+ }
+ }
+ } else {
+ LOGE << "Package data pointer was null, this is unexpected and odd." << endl;
+ }
+ }
+
+ online_session->outgoing_messages.clear();
+
+ online_session->outgoing_messages_lock.unlock();
+
+ online_session->connected_peers_lock.unlock();
+}
+
+void Online::HandleConnectionsToBeClosed() {
+ while (online_session->connections_waiting_to_be_closed.empty() == false) {
+ ConnectionToBeClosed& connection = online_session->connections_waiting_to_be_closed.front();
+ CloseConnectionImmediate(connection.conn_id, connection.reason);
+ online_session->connections_waiting_to_be_closed.pop();
+ }
+}
+
+void Online::SendMessageToConnection(NetConnectionID conn_id, char* buffer, uint32_t bytes, bool reliable, bool flush) {
+ net.SendMessageToConnection(conn_id, buffer, bytes, reliable, flush);
+}
+
+void Online::SendStateMessages() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ for (auto& conn : online_session->connected_peers) {
+ if(conn.should_send_state_messages) {
+ {
+ lock_guard<mutex> guard(online_session->state_packages_lock);
+ for (auto& package : online_session->state_packages) {
+ SendTo<OnlineMessages::AngelscriptData>(conn.conn_id, package.first, package.second.data, true);
+ }
+ }
+
+ conn.should_send_state_messages = false;
+ }
+ }
+}
+
+void Online::CheckNewMessages() {
+ // we are locking over a big segment and I/O, but it's not dangerous
+ // calls that could be waiting are not time sensitive.
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+
+ //static SteamNetworkingMessage_t ** msg = (SteamNetworkingMessage_t **)malloc(sizeof(SteamNetworkingMessage_t*) * 1);
+ NetworkingMessage msg;
+
+ for (auto& it : online_session->connected_peers) {
+ int nrOfMessages = net.ReceiveMessageOnConnection(it.conn_id, &msg);
+
+ if (nrOfMessages == -1) {
+ LOGE << "Invalid connection handle: " << it.conn_id << endl;
+ }
+
+ for (int i = 0; i < nrOfMessages; i++) {
+ PackageHeader package_header;
+ static vector<char> data;
+ DecompressData(data, msg.GetData(), msg.GetSize());
+ package_header.Deserialize(data.data());
+
+ if (package_header.package_type == PackageHeader::Type::MESSAGE_OBJECT) {
+ online_session->incoming_messages_lock.lock();
+ online_session->incoming_messages.push_back({it.peer_id, package_header.message_ref});
+ online_session->incoming_messages_lock.unlock();
+ } else {
+ LOGE << "Unrecognized package type: " << (int)package_header.package_type << endl;
+ }
+ msg.Release();
+
+ }
+ }
+}
+
+binn* PackageHeader::Serialize() {
+ binn* l = binn_list();
+
+ binn_list_add_int8(l, (int8_t)package_type);
+ if(package_type == Type::MESSAGE_OBJECT) {
+ binn_list_add_int8(l, (int8_t)message_handler.GetMessageType(message_ref));
+ binn* serialized_object = message_handler.Serialize(message_ref);
+ if(serialized_object != nullptr) {
+ binn_list_add_object(l,serialized_object);
+ } else {
+ LOGE << "Serialization of PackageHeader message resulted in a nullptr" << endl;
+ }
+ binn_free(serialized_object);
+ }
+
+ return l;
+}
+
+void PackageHeader::Deserialize(void* data) {
+ //Do a raw read, don't worry about copying the data first.
+ binn* l = (binn*)data;
+
+ int pos = 1;
+
+ int8_t v_package_type;
+ if(binn_list_get_int8(l, pos++, &v_package_type) == false) {
+ LOGE << "Failed to read package type from binn message" << endl;
+ }
+ this->package_type = (Type)v_package_type;
+
+ if(package_type == Type::MESSAGE_OBJECT) {
+ int8_t message_type;
+ binn_list_get_int8(l, pos++, &message_type);
+
+ void* serialized_object;
+ binn_list_get_object(l, pos++, &serialized_object);
+
+ if(serialized_object != nullptr) {
+ message_ref = message_handler.Deserialize((OnlineMessageType)message_type, (binn*)serialized_object);
+ } else {
+ LOGE << "Got a nullptr when trying to fetch a MESSAGE_OBJECT binn from a PackageHeader" << endl;
+ }
+ }
+}