diff --git a/include/Engine/Core/EPlayerSpawned.h b/include/Engine/Core/EPlayerSpawned.h new file mode 100644 index 00000000..a5700ed3 --- /dev/null +++ b/include/Engine/Core/EPlayerSpawned.h @@ -0,0 +1,19 @@ +#ifndef EPlayerSpawned_h__ +#define EPlayerSpawned_h__ + +#include "Core/Event.h" +#include "Core/EntityWrapper.h" + +namespace Events +{ + +struct PlayerSpawned : Event +{ + int PlayerID; + EntityWrapper Player; + EntityWrapper Spawner; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EntityWrapper.h b/include/Engine/Core/EntityWrapper.h index 3ba4e087..711f5045 100644 --- a/include/Engine/Core/EntityWrapper.h +++ b/include/Engine/Core/EntityWrapper.h @@ -25,6 +25,8 @@ struct EntityWrapper bool HasComponent(const std::string& componentName); EntityWrapper Parent(); + EntityWrapper FirstChildByName(const std::string& name); + bool IsChildOf(EntityWrapper potentialParent); bool Valid(); ComponentWrapper operator[](const char* componentName); diff --git a/include/Engine/Core/Octree.h b/include/Engine/Core/Octree.h index bbd27b3c..8bac5503 100644 --- a/include/Engine/Core/Octree.h +++ b/include/Engine/Core/Octree.h @@ -111,7 +111,7 @@ struct Child std::vector& m_StaticObjectsRef; std::vector& m_DynamicObjectsRef; - inline bool hasChildren() const; + bool hasChildren() const; int childIndexContainingPoint(const glm::vec3& point) const; std::vector childIndicesContainingBox(const AABB& box) const; }; diff --git a/include/Engine/Core/Transform.h b/include/Engine/Core/Transform.h index 474a7bdb..3b0811c9 100644 --- a/include/Engine/Core/Transform.h +++ b/include/Engine/Core/Transform.h @@ -3,13 +3,18 @@ #include "../GLM.h" #include "World.h" +#include "EntityWrapper.h" namespace Transform { +glm::vec3 AbsolutePosition(EntityWrapper entity); glm::vec3 AbsolutePosition(World* world, EntityID entity); +glm::quat AbsoluteOrientation(EntityWrapper entity); glm::quat AbsoluteOrientation(World* world, EntityID entity); +glm::vec3 AbsoluteScale(EntityWrapper entity); glm::vec3 AbsoluteScale(World* world, EntityID entity); +glm::mat4 ModelMatrix(EntityWrapper entity); glm::mat4 ModelMatrix(EntityID entity, World* world); } diff --git a/include/Engine/Editor/EditorCameraInputController.h b/include/Engine/Editor/EditorCameraInputController.h new file mode 100644 index 00000000..66c7952a --- /dev/null +++ b/include/Engine/Editor/EditorCameraInputController.h @@ -0,0 +1,115 @@ +#ifndef EditorCameraInputController_h__ +#define EditorCameraInputController_h__ + +#include +#include "../Input/FirstPersonInputController.h" +#include "../Core/EMousePress.h" +#include "../Core/EMouseRelease.h" +#include "../Core/EMouseScroll.h" +#include "../Core/ConfigFile.h" + +template +class EditorCameraInputController : public FirstPersonInputController +{ +public: + EditorCameraInputController(EventBroker* eventBroker, unsigned int playerID) + : FirstPersonInputController(eventBroker, playerID) + { + EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &EditorCameraInputController::OnMousePress); + EVENT_SUBSCRIBE_MEMBER(m_EMouseRelease, &EditorCameraInputController::OnMouseRelease); + EVENT_SUBSCRIBE_MEMBER(m_EMouseScroll, &EditorCameraInputController::OnMouseScroll); + + m_Config = ResourceManager::Load("Config.ini"); + m_SpeedMultiplier = m_Config->Get("Editor.CameraSpeed", 3.f); + } + + virtual const glm::vec3 Movement() const override { return m_Movement * m_SpeedMultiplier; } + + void Enable() { m_Enabled = true; } + void Disable() { m_Enabled = false; } + + virtual bool OnCommand(const Events::InputCommand& e) override + { + if (glm::abs(e.Value) > 0 && !m_MouseLocked) { + return false; + } + + ImGuiIO& io = ImGui::GetIO(); + if (glm::abs(e.Value) > 0 && (io.WantCaptureKeyboard || io.WantCaptureMouse)) { + return false; + } + + if (e.Command == "Jump") { + if (e.Value > 0) { + m_Movement.y = glm::max(e.Value, 1.f); + } else { + m_Movement.y = 0.f; + } + } + + if (e.Command == "Crouch") { + if (e.Value > 0) { + m_Movement.y = glm::min(-e.Value, -1.f); + } else { + m_Movement.y = 0.f; + } + } + + if (e.Command == "Sprint") { + if (e.Value > 0) { + m_SpeedMultiplier *= 2.f; + } else { + m_SpeedMultiplier /= 2.f; + } + } + + return FirstPersonInputController::OnCommand(e); + } + +protected: + ConfigFile* m_Config; + bool m_Enabled = false; + float m_SpeedMultiplier = 1.f; + + EventRelay m_EMousePress; + bool OnMousePress(const Events::MousePress& e) + { + if (!m_Enabled) { + return false; + } + + if (e.Button == GLFW_MOUSE_BUTTON_2) { + ImGuiIO& io = ImGui::GetIO(); + if (!io.WantCaptureMouse) { + LockMouse(); + } + } + return true; + } + EventRelay m_EMouseRelease; + bool OnMouseRelease(const Events::MouseRelease& e) + { + if (!m_Enabled) { + return false; + } + + if (e.Button == GLFW_MOUSE_BUTTON_2) { + UnlockMouse(); + } + return true; + } + EventRelay m_EMouseScroll; + bool OnMouseScroll(const Events::MouseScroll& e) + { + if (!m_Enabled) { + return false; + } + + m_SpeedMultiplier += e.DeltaY * (0.1f * m_SpeedMultiplier); + m_Config->Set("Editor.CameraSpeed", m_SpeedMultiplier); + m_Config->SaveToDisk(); + return true; + } +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Editor/EditorSystem.h b/include/Engine/Editor/EditorSystem.h index b354ddf3..2db24ba3 100644 --- a/include/Engine/Editor/EditorSystem.h +++ b/include/Engine/Editor/EditorSystem.h @@ -1,7 +1,6 @@ #include "../Core/System.h" #include "../Rendering/IRenderer.h" #include "../Rendering/Camera.h" -#include "../Rendering/DebugCameraInputController.h" #include "../Rendering/ESetCamera.h" #include "../Core/World.h" #include "../Core/SystemPipeline.h" @@ -10,8 +9,10 @@ #include "../Core/EntityFileParser.h" #include "../Core/EntityFileWriter.h" #include "../Core/EMousePress.h" +#include "../Input/EInputCommand.h" #include "EditorGUI.h" #include "EditorStats.h" +#include "EditorCameraInputController.h" class EditorSystem : public ImpureSystem { @@ -21,18 +22,24 @@ public: void Update(double dt); + void Enable(); + void Disable(); + private: IRenderer* m_Renderer; RenderFrame* m_RenderFrame; World* m_EditorWorld; SystemPipeline* m_EditorWorldSystemPipeline; - Camera* m_EditorCamera; - EntityWrapper m_Camera = EntityWrapper::Invalid; - DebugCameraInputController* m_DebugCameraInputController; + //Camera* m_EditorCamera; + EntityWrapper m_EditorCamera = EntityWrapper::Invalid; + EntityWrapper m_ActualCamera = EntityWrapper::Invalid; + EditorCameraInputController* m_EditorCameraInputController; EditorGUI* m_EditorGUI; EditorStats* m_EditorStats; // State + double m_LastTime = 0.f; + bool m_Enabled = true; EditorGUI::WidgetMode m_WidgetMode = EditorGUI::WidgetMode::Translate; EntityWrapper m_Widget = EntityWrapper::Invalid; EntityWrapper m_CurrentSelection = EntityWrapper::Invalid; @@ -56,4 +63,8 @@ private: bool OnMousePress(const Events::MousePress& e); EventRelay m_EWidgetDelta; bool OnWidgetDelta(const Events::WidgetDelta& e); + EventRelay m_EInputCommand; + bool OnInputCommand(const Events::InputCommand& e); + EventRelay m_ESetCamera; + bool OnSetCamera(const Events::SetCamera& e); }; \ No newline at end of file diff --git a/include/Engine/Input/EInputCommand.h b/include/Engine/Input/EInputCommand.h index 9ec897d3..1e150786 100644 --- a/include/Engine/Input/EInputCommand.h +++ b/include/Engine/Input/EInputCommand.h @@ -9,7 +9,7 @@ namespace Events struct InputCommand : Event { /** Numerical ID of the player. */ - unsigned int PlayerID; + int PlayerID; /** The command that was sent. */ std::string Command; /** The value of the command. */ diff --git a/include/Engine/Input/FirstPersonInputController.h b/include/Engine/Input/FirstPersonInputController.h index d8584494..426670c4 100644 --- a/include/Engine/Input/FirstPersonInputController.h +++ b/include/Engine/Input/FirstPersonInputController.h @@ -9,62 +9,119 @@ template class FirstPersonInputController : public InputController { public: - FirstPersonInputController(EventBroker* eventBroker, unsigned int playerID) - : InputController(eventBroker) - , m_PlayerID(playerID) - { - EVENT_SUBSCRIBE_MEMBER(m_ELockMouse, &FirstPersonInputController::OnLockMouse); - EVENT_SUBSCRIBE_MEMBER(m_EUnlockMouse, &FirstPersonInputController::OnUnlockMouse); - } + FirstPersonInputController(EventBroker* eventBroker, int playerID); - const glm::quat Orientation() const { return m_Orientation; } + virtual const glm::vec3 Movement() const { return m_Movement; } + virtual const glm::vec3 Rotation() const { return m_Rotation; } + virtual bool Jumping() const { return m_Jumping; } + virtual bool Crouching() const { return m_Crouching; } - void LockMouse() - { - Events::LockMouse e; - m_EventBroker->Publish(e); - m_MouseLocked = true; - } + void LockMouse(); + void UnlockMouse(); + virtual bool OnCommand(const Events::InputCommand& e) override; + virtual void Reset(); - void UnlockMouse() - { - Events::UnlockMouse e; - m_EventBroker->Publish(e); - m_MouseLocked = false; - } +protected: + const int m_PlayerID; + bool m_MouseLocked = false; + glm::vec3 m_Rotation; + glm::vec3 m_Movement; + bool m_Jumping = false; + bool m_Crouching = false; + + EventRelay m_ELockMouse; + bool OnLockMouse(const Events::LockMouse& e); + EventRelay m_EUnlockMouse; + bool OnUnlockMouse(const Events::UnlockMouse& e); +}; - virtual bool OnCommand(const Events::InputCommand& e) override - { - if (m_PlayerID != e.PlayerID) { - return false; - } +template +FirstPersonInputController::FirstPersonInputController(EventBroker* eventBroker, int playerID) + : InputController(eventBroker) + , m_PlayerID(playerID) +{ + EVENT_SUBSCRIBE_MEMBER(m_ELockMouse, &FirstPersonInputController::OnLockMouse); + EVENT_SUBSCRIBE_MEMBER(m_EUnlockMouse, &FirstPersonInputController::OnUnlockMouse); +} - if (m_MouseLocked) { - if (e.Command == "Pitch") { - float val = glm::radians(e.Value); - m_Orientation = m_Orientation * glm::angleAxis(-val, glm::vec3(1, 0, 0)); - return true; - } +template +void FirstPersonInputController::Reset() +{ + m_Rotation = glm::vec3(0.f, 0.f, 0.f); + m_Jumping = false; +} - if (e.Command == "Yaw") { - float val = glm::radians(e.Value); - m_Orientation = glm::angleAxis(-val, glm::vec3(0, 1, 0)) * m_Orientation; - return true; - } - } +template +void FirstPersonInputController::LockMouse() +{ + Events::LockMouse e; + m_EventBroker->Publish(e); + m_MouseLocked = true; +} +template +void FirstPersonInputController::UnlockMouse() +{ + Events::UnlockMouse e; + m_EventBroker->Publish(e); + m_MouseLocked = false; +} + +template +bool FirstPersonInputController::OnCommand(const Events::InputCommand& e) +{ + if (m_PlayerID != e.PlayerID) { return false; } -protected: - const unsigned int m_PlayerID; - glm::quat m_Orientation; - bool m_MouseLocked = false; - - EventRelay m_ELockMouse; - bool OnLockMouse(const Events::LockMouse& e) { m_MouseLocked = true; return true; } - EventRelay m_EUnlockMouse; - bool OnUnlockMouse(const Events::UnlockMouse& e) { m_MouseLocked = false; return true; } -}; + if (e.Command == "Pitch") { + float val = glm::radians(e.Value); + m_Rotation.x += -val; + //m_Rotation.x = glm::clamp(m_Rotation.x, -glm::half_pi(), glm::half_pi()); + } + + if (e.Command == "Yaw") { + float val = glm::radians(e.Value); + m_Rotation.y += -val; + } + + if (e.Command == "Forward" || e.Command == "Right") { + if (e.Command == "Forward") { + float val = glm::clamp(e.Value, -1.f, 1.f); + m_Movement.z = -val; + } + if (e.Command == "Right") { + float val = glm::clamp(e.Value, -1.f, 1.f); + m_Movement.x = val; + } + if (glm::length2(m_Movement) > 0) { + m_Movement = glm::normalize(m_Movement); + } + } + + if (e.Command == "Jump") { + m_Jumping = e.Value > 0; + } + + if (e.Command == "Crouch") { + m_Crouching = e.Value > 0; + } + + return true; +} + +template +bool FirstPersonInputController::OnUnlockMouse(const Events::UnlockMouse& e) +{ + m_MouseLocked = false; + return true; +} + +template +bool FirstPersonInputController::OnLockMouse(const Events::LockMouse& e) +{ + m_MouseLocked = true; + return true; +} #endif \ No newline at end of file diff --git a/include/Engine/Network/Client.h b/include/Engine/Network/Client.h index 96511baa..92d78f4a 100644 --- a/include/Engine/Network/Client.h +++ b/include/Engine/Network/Client.h @@ -20,6 +20,7 @@ #include "Input/EInputCommand.h" #include "Core/EPlayerDamage.h" #include "Network/EInterpolate.h" +#include "Core/EPlayerSpawned.h" class Client : public Network { @@ -39,14 +40,14 @@ private: char readBuf[INPUTSIZE] = { 0 }; // Packet loss logic - unsigned int m_PacketID = 0; - unsigned int m_PreviousPacketID = 0; - unsigned int m_SendPacketID = 0; + PacketID m_PacketID = 0; + PacketID m_PreviousPacketID = 0; + PacketID m_SendPacketID = 0; // Game logic World* m_World; std::string m_PlayerName; - int m_PlayerID = -1; + PlayerID m_PlayerID = -1; EntityID m_ServerEntityID = std::numeric_limits::max(); bool m_IsConnected = false; // Server Client Lookup map @@ -57,25 +58,27 @@ private: std::unordered_map m_ClientIDToServerID; // Network logic - PlayerDefinition m_PlayerDefinitions[MAXCONNECTIONS]; + PlayerDefinition m_PlayerDefinitions[8]; SnapshotDefinitions m_NextSnapshot; double m_DurationOfPingTime; std::clock_t m_StartPingTime; + std::clock_t m_TimeSinceSentInputs; + unsigned int m_SendInputIntervalMs; std::vector m_InputCommandBuffer; // Private member functions void readFromServer(); - int receive(char* data, size_t length); + int receive(char* data); void send(Packet& packet); void connect(); void disconnect(); - void ping(); void parseMessageType(Packet& packet); void updateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID, const std::string& componentType); void parseConnect(Packet& packet); void parsePlayerConnected(Packet& packet); void parsePing(); - void parseServerPing(); + void parseKick(); + void parsePlayersSpawned(Packet& packet); void InterpolateFields(Packet & packet, const ComponentInfo & componentInfo, const EntityID & entityID, const std::string & componentType); void parseSnapshot(Packet& packet); void identifyPacketLoss(); diff --git a/include/Engine/Network/EPlayerDisconnected.h b/include/Engine/Network/EPlayerDisconnected.h new file mode 100644 index 00000000..278a7b1f --- /dev/null +++ b/include/Engine/Network/EPlayerDisconnected.h @@ -0,0 +1,18 @@ +#ifndef Events_PlayerDisconnected +#define Events_PlayerDisconnected + +#include "Core/EventBroker.h" +#include "Core/Entity.h" + +namespace Events +{ + +struct PlayerDisconnected : public Event +{ + unsigned int PlayerID; + EntityID Entity; +}; + +} + +#endif diff --git a/include/Engine/Network/MessageType.h b/include/Engine/Network/MessageType.h index f0026190..13ac5e9d 100644 --- a/include/Engine/Network/MessageType.h +++ b/include/Engine/Network/MessageType.h @@ -5,16 +5,17 @@ // Used to determine what type of message was sent. enum class MessageType { - Connect, - Disconnect, - ClientPing, - ServerPing, - Message, - Snapshot, + Connect, + Disconnect, + Ping, + Message, + Snapshot, OnInputCommand, OnPlayerDamage, PlayerConnected, - BecomePlayer + BecomePlayer, + Kick, + OnPlayerSpawned }; #endif diff --git a/include/Engine/Network/Network.h b/include/Engine/Network/Network.h index 1464a96f..480ac602 100644 --- a/include/Engine/Network/Network.h +++ b/include/Engine/Network/Network.h @@ -1,13 +1,21 @@ #ifndef Network_h__ #define Network_h__ +#include + #include "Core/World.h" #include "Core/EventBroker.h" #include "Network/Packet.h" +#include "Network/NetworkData.h" +#include "Core/ResourceManager.h" +#include "Core/ConfigFile.h" +#include +#include -#define MAXCONNECTIONS 8 #define INPUTSIZE 4097 -#define TIMEOUTMS 15000 +typedef unsigned int PlayerID; +typedef unsigned int PacketID; +typedef unsigned int UserID; class Network { @@ -15,6 +23,17 @@ public: virtual ~Network() { }; virtual void Start(World* m_world, EventBroker *eventBroker) = 0; virtual void Update() = 0; +protected: + // For Debug + bool isReadingData = false; + NetworkData m_NetworkData; + unsigned int m_SaveDataIntervalMs = 1000; + std::clock_t m_SaveDataTimer; + unsigned int m_MaxConnections; + unsigned int m_TimeoutMs; + void saveToFile(); + void updateNetworkData(); + void initialize(); }; #endif \ No newline at end of file diff --git a/include/Engine/Network/NetworkData.h b/include/Engine/Network/NetworkData.h new file mode 100644 index 00000000..87f7a215 --- /dev/null +++ b/include/Engine/Network/NetworkData.h @@ -0,0 +1,18 @@ +#ifndef NetworkData_h__ +#define NetworkData_h__ +#include + +struct NetworkData { + unsigned int TotalTime = 0; + unsigned int TotalDataReceived = 0; + unsigned int TotalDataSent = 0; + unsigned int AmountOfMessagesReceived = 0; + unsigned int AmountOfMessagesSent = 0; + // Interval based + unsigned int DataReceivedThisInterval = 0; + unsigned int DataSentThisInterval = 0; + // pair: first=reveived, second=send + std::vector> BandwidthBytes; +}; + +#endif diff --git a/include/Engine/Network/Packet.h b/include/Engine/Network/Packet.h index 2891bf87..d38ddf58 100644 --- a/include/Engine/Network/Packet.h +++ b/include/Engine/Network/Packet.h @@ -55,12 +55,14 @@ public: char* Data() { return m_Data; }; unsigned int DataReadSize() { return m_ReturnDataOffset; } unsigned int MaxSize() { return m_MaxPacketSize; } + unsigned int HeaderSize() { return m_HeaderSize; } private: char* m_Data; unsigned int m_ReturnDataOffset = 0; int m_Offset = 0; unsigned int m_MaxPacketSize = 512; + unsigned int m_HeaderSize = 0; void resizeData(); }; diff --git a/include/Engine/Network/Server.h b/include/Engine/Network/Server.h index 8aabceba..e7c745e7 100644 --- a/include/Engine/Network/Server.h +++ b/include/Engine/Network/Server.h @@ -14,6 +14,8 @@ #include "../Network/Network.h" #include "Input/EInputCommand.h" #include "Core/EPlayerDamage.h" +#include "Network/EPlayerDisconnected.h" +#include "Core/EPlayerSpawned.h" class Server : public Network { @@ -29,7 +31,7 @@ private: boost::asio::ip::udp::socket m_Socket; // Sending messages to client logic - PlayerDefinition m_PlayerDefinitions[MAXCONNECTIONS]; + PlayerDefinition m_PlayerDefinitions[8]; // std::vector m_ConnectedUsers; char readBuffer[INPUTSIZE] = { 0 }; int bytesRead = 0; @@ -38,8 +40,8 @@ private: std::clock_t previousSnapshotMessage = std::clock(); std::clock_t timOutTimer = std::clock(); // How often we send messages (milliseconds) - int intervalMs = 1000; - int snapshotInterval = 50; + int pingIntervalMs; + int snapshotInterval; int checkTimeOutInterval = 100; //Timers @@ -50,32 +52,36 @@ private: EventBroker* m_EventBroker; // Packet loss logic - unsigned int m_PacketID = 0; - unsigned int m_PreviousPacketID = 0; + PacketID m_PacketID = 0; + PacketID m_PreviousPacketID = 0; // Private member functions - int receive(char* data, size_t length); + int receive(char* data); void readFromClients(); - void send(Packet& packet, int playerID); + void send(Packet& packet, UserID user); + void send(PlayerID player, Packet& packet); void send(Packet& packet); void broadcast(Packet& packet); void sendSnapshot(); void sendPing(); void checkForTimeOuts(); - void disconnect(int i); + void disconnect(UserID user); void parseMessageType(Packet& packet); void parseOnInputCommand(Packet& packet); void parseOnPlayerDamage(Packet& packet); void parseConnect(Packet& packet); void parseDisconnect(); void parseClientPing(); - void parseServerPing(); + void parsePing(); void identifyPacketLoss(); void createPlayer(); - int GetPlayerIDFromEndpoint(boost::asio::ip::udp::endpoint endpoint); + void kick(PlayerID player); + PlayerID GetPlayerIDFromEndpoint(boost::asio::ip::udp::endpoint endpoint); // Debug event EventRelay m_EInputCommand; bool OnInputCommand(const Events::InputCommand& e); + EventRelay m_EPlayerSpawned; + bool OnPlayerSpawned(const Events::PlayerSpawned& e); }; #endif diff --git a/include/Engine/Rendering/DebugCameraInputController.h b/include/Engine/Rendering/DebugCameraInputController.h deleted file mode 100644 index 5ce85b3f..00000000 --- a/include/Engine/Rendering/DebugCameraInputController.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef DebugCameraInputController_h__ -#define DebugCameraInputController_h__ - -#include -#include "../Input/FirstPersonInputController.h" -#include "../Core/EMousePress.h" -#include "../Core/EMouseRelease.h" - -template -class DebugCameraInputController : public FirstPersonInputController -{ -public: - DebugCameraInputController(EventBroker* eventBroker, unsigned int playerID) - : FirstPersonInputController(eventBroker, playerID) - { - EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &DebugCameraInputController::OnMousePress); - EVENT_SUBSCRIBE_MEMBER(m_EMouseRelease, &DebugCameraInputController::OnMouseRelease); - } - - void SetPosition(const glm::vec3 position) { m_Position = position; } - void SetOrientation(const glm::quat orientation) { m_Orientation = orientation; } - - const glm::vec3 Position() const { return m_Position; } - void SetBaseSpeed(float speed) { m_BaseSpeed = speed; } - - virtual bool OnCommand(const Events::InputCommand& e) override - { - ImGuiIO& io = ImGui::GetIO(); - - if (!io.WantCaptureKeyboard) { - if (e.Command == "Right") { - float value = std::max(-1.f, std::min(e.Value, 1.f)); - m_Velocity.x = value; - } - if (e.Command == "Forward") { - float value = std::max(-1.f, std::min(e.Value, 1.f)); - m_Velocity.z = -value; - } - if (e.Command == "Sprint") { - if (e.Value > 0.f) { - m_Speed = m_BaseSpeed * 2.f * (e.Value); - } else { - m_Speed = m_BaseSpeed; - } - } - } - - return FirstPersonInputController::OnCommand(e); - } - - void Update(double dt) - { - if (glm::length2(m_Velocity) > 0) { - m_Position += m_Orientation * (glm::normalize(m_Velocity) * m_Speed * (float)dt); - } - } - -protected: - glm::vec3 m_Position = glm::vec3(0, 0, 0); - glm::vec3 m_Velocity = glm::vec3(0, 0, 0); - float m_BaseSpeed = 2.0f; - float m_Speed = m_BaseSpeed; - EventRelay m_EMousePress; - bool OnMousePress(const Events::MousePress& e) - { - if (e.Button == GLFW_MOUSE_BUTTON_2) { - ImGuiIO& io = ImGui::GetIO(); - if (!io.WantCaptureMouse) { - LockMouse(); - } - } - return true; - } - EventRelay m_EMouseRelease; - bool OnMouseRelease(const Events::MouseRelease& e) - { - if (e.Button == GLFW_MOUSE_BUTTON_2) { - UnlockMouse(); - } - return true; - } -}; - -#endif \ No newline at end of file diff --git a/include/Engine/Rendering/RenderSystem.h b/include/Engine/Rendering/RenderSystem.h index 9b8e5a9f..005d2eb7 100644 --- a/include/Engine/Rendering/RenderSystem.h +++ b/include/Engine/Rendering/RenderSystem.h @@ -15,7 +15,7 @@ #include "Renderer.h" #include "PointLightJob.h" #include "../Core/Transform.h" -#include "DebugCameraInputController.h" +#include "../Core/EPlayerSpawned.h" class RenderSystem : public ImpureSystem { @@ -30,6 +30,7 @@ private: RenderFrame* m_RenderFrame; Camera* m_Camera; EntityWrapper m_CurrentCamera = EntityWrapper::Invalid; + EntityWrapper m_LocalPlayer = EntityWrapper::Invalid; EventRelay m_ESetCamera; bool OnSetCamera(Events::SetCamera &event); @@ -41,6 +42,9 @@ private: void fillModels(std::list>& jobs); void fillLight(std::list>& jobs); + + EventRelay m_EPlayerSpawned; + bool OnPlayerSpawned(Events::PlayerSpawned& e); }; #endif \ No newline at end of file diff --git a/include/Game/Systems/InterpolationSystem.h b/include/Game/Systems/InterpolationSystem.h index 4b05571b..1e345c91 100644 --- a/include/Game/Systems/InterpolationSystem.h +++ b/include/Game/Systems/InterpolationSystem.h @@ -10,11 +10,12 @@ #include "Common.h" #include "Core/System.h" #include "Core/EventBroker.h" +#include "Core/ResourceManager.h" +#include "Core/ConfigFile.h" +#include "Core/EPlayerSpawned.h" #include "Network/EInterpolate.h" -#define SNAPSHOTINTERVAL 0.05f - class InterpolationSystem : public PureSystem { struct Transform @@ -25,29 +26,28 @@ class InterpolationSystem : public PureSystem double interpolationTime; }; public: - InterpolationSystem(World* world, EventBroker* eventBroker) - : System(world, eventBroker) - , PureSystem("Transform") - { - EVENT_SUBSCRIBE_MEMBER(m_EInterpolate, &InterpolationSystem::OnInterpolate); - } + InterpolationSystem(World* world, EventBroker* eventBroker); ~InterpolationSystem() { } virtual void UpdateComponent(EntityWrapper& entity, ComponentWrapper& transform, double dt) override; private: std::unordered_map m_NextTransform; std::unordered_map m_LastReceivedTransform; + EntityWrapper m_LocalPlayer = EntityWrapper::Invalid; //glm::vec3 vectorInterpolation(glm::vec3 prev, glm::vec3 next, double currentTime); template T vectorInterpolation(T prev, T next, double currentTime) { T difference = next - prev; - T vector = (difference / SNAPSHOTINTERVAL) * static_cast(currentTime); + T vector = (difference / m_SnapshotInterval) * static_cast(currentTime); return vector; } + float m_SnapshotInterval; EventRelay m_EInterpolate; bool InterpolationSystem::OnInterpolate(const Events::Interpolate& e); + EventRelay m_EPlayerSpawned; + bool OnPlayerSpawned(Events::PlayerSpawned& e); }; #endif diff --git a/include/Game/Systems/PlayerMovementSystem.h b/include/Game/Systems/PlayerMovementSystem.h index 1be61bdd..f39740ec 100644 --- a/include/Game/Systems/PlayerMovementSystem.h +++ b/include/Game/Systems/PlayerMovementSystem.h @@ -1,14 +1,23 @@ #include "Common.h" #include "GLM.h" #include "Core/System.h" +#include "Core/EPlayerSpawned.h" +#include "Input/FirstPersonInputController.h" +#include -class PlayerMovementSystem : public PureSystem +class PlayerMovementSystem : public ImpureSystem, PureSystem { public: - PlayerMovementSystem(World* world, EventBroker* eventBroker) - : System(world, eventBroker) - , PureSystem("Player") - { } + PlayerMovementSystem(World* world, EventBroker* eventBroker); + ~PlayerMovementSystem(); + virtual void Update(double dt) override; virtual void UpdateComponent(EntityWrapper& entity, ComponentWrapper& component, double dt); + +private: + // State + std::unordered_map*> m_PlayerInputControllers; + + EventRelay m_EPlayerSpawned; + bool OnPlayerSpawned(Events::PlayerSpawned& e); }; \ No newline at end of file diff --git a/include/Game/Systems/PlayerSpawnSystem.h b/include/Game/Systems/PlayerSpawnSystem.h index 8ade03a6..b0ff1d79 100644 --- a/include/Game/Systems/PlayerSpawnSystem.h +++ b/include/Game/Systems/PlayerSpawnSystem.h @@ -2,6 +2,9 @@ #include "Input/EInputCommand.h" #include "Systems/SpawnerSystem.h" #include "Events/ESpawnerSpawn.h" +#include "Core/EPlayerSpawned.h" +#include "Rendering/ESetCamera.h" +#include "Core/ConfigFile.h" class PlayerSpawnSystem : public ImpureSystem { @@ -11,8 +14,18 @@ public: virtual void Update(double dt) override; private: + struct SpawnRequest + { + int PlayerID; + ComponentInfo::EnumType Team; + }; + + bool m_NetworkEnabled = false; + std::vector m_SpawnRequests; + std::map m_PlayerEntities; + EventRelay m_OnInputCommand; bool OnInputCommand(const Events::InputCommand& e); - - std::vector m_SpawnRequests; + EventRelay m_OnPlayerSpawnerd; + bool OnPlayerSpawned(Events::PlayerSpawned& e); }; \ No newline at end of file diff --git a/resources/DefaultConfig.ini b/resources/DefaultConfig.ini index fb490ec8..86dc735c 100644 --- a/resources/DefaultConfig.ini +++ b/resources/DefaultConfig.ini @@ -1,11 +1,13 @@ [Debug] LogLevel=1 LoadMap= -EditorEnabled=false ; if true -> Pool allocation is not used when calling Allocate/Free, just use regular dynamic allocation. ; if false -> Use pool allocation. DisableMemoryPool=false +[Editor] +CameraSpeed=3 + [Video] Fullscreen=false VSYNC=false @@ -19,6 +21,11 @@ IsServer=false Name=Bob Address=127.0.0.1 Port=13 +MaxConnections=8 +SnapshotInterval=0.05 +SendInputIntervalMs=33 +PingIntervalMs= 1000 +TimeoutMs=15000 [Multithreading] ResourceLoading=true diff --git a/resources/Schema/Components/Player.xml b/resources/Schema/Components/Player.xml index caefd6e6..b51326aa 100644 --- a/resources/Schema/Components/Player.xml +++ b/resources/Schema/Components/Player.xml @@ -1,8 +1,5 @@ - - false - false - false - false + 3 + 1.5 \ No newline at end of file diff --git a/resources/Schema/Components/Player.xsd b/resources/Schema/Components/Player.xsd index 1a315a35..13948dc2 100644 --- a/resources/Schema/Components/Player.xsd +++ b/resources/Schema/Components/Player.xsd @@ -9,11 +9,8 @@ - - - - - + + diff --git a/resources/Schema/Entities/CollidableCube.xml b/resources/Schema/Entities/CollidableCube.xml new file mode 100644 index 00000000..ebba54be --- /dev/null +++ b/resources/Schema/Entities/CollidableCube.xml @@ -0,0 +1,15 @@ + + + + + + + + Models/Core/UnitCube.obj + + + + + + + diff --git a/resources/Schema/Entities/MovementTest.xml b/resources/Schema/Entities/MovementTest.xml index dc271908..40de747a 100644 --- a/resources/Schema/Entities/MovementTest.xml +++ b/resources/Schema/Entities/MovementTest.xml @@ -6,66 +6,178 @@ - - - - - - - - + Models/Core/UnitCube.mesh + false - - + + - + - - - - - - - - Models/Assault.mesh - - - + + + Schema/Entities/Player.xml + + + + + + - + + + + + + + + + Models/Assault.mesh + + + + + + + + + + + + + Models/Assault.mesh + + + + + + + + + + + + + + + Models/DirectionalLightWidget.mesh + + + + - + + + + Models/Test/ObstacleCourse.mesh + + + + + + - - - Models/Core/UnitCube.mesh - + false - - - + + + + + + + + Models/Core/UnitCube.mesh + false + + + + + + + + + + + + + + Models/Core/UnitCube.mesh + false + + + + + + + + + + + + + Schema/Entities/Player.xml + + + + + + + + + + + + + + + + + Models/Assault.mesh + + + + + + + + + + + + + Models/Assault.mesh + + + + + + + + + + diff --git a/resources/Schema/Entities/Player.xml b/resources/Schema/Entities/Player.xml index 485c6cff..4316091b 100644 --- a/resources/Schema/Entities/Player.xml +++ b/resources/Schema/Entities/Player.xml @@ -2,17 +2,120 @@ - - + + + + - - Models/Core/UnitSphere.mesh - - - - + + + + + + 5 + + + + + + + + + + - + + + + + + + + + + + + + + Fonts/DroidSans.ttf,100 + + false + + + + + + + + + + + + + Models/Camera.mesh + + + + + + + + + + + + + + + Models/Camera.mesh + false + + + + + + + + + + + + Models/AssaultHeadless.mesh + + + + + + + + + + + Models/Core/UnitCube.mesh + + false + + + + + + + + + + + + Models/Core/UnitCube.mesh + + false + + + + + + + + diff --git a/resources/Schema/Types.xsd b/resources/Schema/Types.xsd index fb584c64..8d9bf289 100644 --- a/resources/Schema/Types.xsd +++ b/resources/Schema/Types.xsd @@ -16,6 +16,9 @@ + + + diff --git a/src/Engine/Collision/CollisionSystem.cpp b/src/Engine/Collision/CollisionSystem.cpp index 0c77c306..ba841e36 100644 --- a/src/Engine/Collision/CollisionSystem.cpp +++ b/src/Engine/Collision/CollisionSystem.cpp @@ -31,7 +31,7 @@ void CollisionSystem::UpdateComponent(EntityWrapper& entity, ComponentWrapper& c } if (Collision::AABBVsAABB(boxA, boxB, resolutionVector)) { (glm::vec3&)cTransform["Position"] += resolutionVector; - cPhysics["Velocity"] = glm::vec3(0, 0, 0); + ((glm::vec3&)cPhysics["Velocity"]).y = 0.f; } } diff --git a/src/Engine/Core/EntityWrapper.cpp b/src/Engine/Core/EntityWrapper.cpp index 98b9c7d4..55d341e1 100644 --- a/src/Engine/Core/EntityWrapper.cpp +++ b/src/Engine/Core/EntityWrapper.cpp @@ -5,6 +5,9 @@ const EntityWrapper EntityWrapper::Invalid = EntityWrapper(nullptr, EntityID_Inv bool EntityWrapper::HasComponent(const std::string& componentName) { + if (!Valid()) { + return false; + } return World->HasComponent(ID, componentName); } @@ -17,6 +20,34 @@ EntityWrapper EntityWrapper::Parent() } } +EntityWrapper EntityWrapper::FirstChildByName(const std::string& name) +{ + auto itPair = this->World->GetChildren(this->ID); + if (itPair.first == itPair.second) { + return EntityWrapper::Invalid; + } + + for (auto it = itPair.first; it != itPair.second; ++it) { + if (this->World->GetName(it->second) == name) { + return EntityWrapper(this->World, it->second); + } + } + + return EntityWrapper::Invalid; +} + +bool EntityWrapper::IsChildOf(EntityWrapper potentialParent) +{ + EntityWrapper entity = *this; + while (entity.Parent().Valid()) { + entity = entity.Parent(); + if (entity == potentialParent) { + return true; + } + } + return false; +} + bool EntityWrapper::Valid() { if (this->World == nullptr) { diff --git a/src/Engine/Core/Octree.cpp b/src/Engine/Core/Octree.cpp index d7feba45..18effea5 100644 --- a/src/Engine/Core/Octree.cpp +++ b/src/Engine/Core/Octree.cpp @@ -276,7 +276,7 @@ std::vector Child::childIndicesContainingBox(const AABB& box) const } } -inline bool Child::hasChildren() const +bool Child::hasChildren() const { return m_Children[0] != nullptr; } diff --git a/src/Engine/Core/Transform.cpp b/src/Engine/Core/Transform.cpp index cbc405a3..c9869014 100644 --- a/src/Engine/Core/Transform.cpp +++ b/src/Engine/Core/Transform.cpp @@ -1,5 +1,10 @@ #include "Core/Transform.h" +glm::vec3 Transform::AbsolutePosition(EntityWrapper entity) +{ + return AbsolutePosition(entity.World, entity.ID); +} + glm::vec3 Transform::AbsolutePosition(World* world, EntityID entity) { glm::vec3 position; @@ -14,6 +19,11 @@ glm::vec3 Transform::AbsolutePosition(World* world, EntityID entity) return position; } +glm::quat Transform::AbsoluteOrientation(EntityWrapper entity) +{ + return AbsoluteOrientation(entity.World, entity.ID); +} + glm::quat Transform::AbsoluteOrientation(World* world, EntityID entity) { glm::quat orientation; @@ -27,6 +37,11 @@ glm::quat Transform::AbsoluteOrientation(World* world, EntityID entity) return orientation; } +glm::vec3 Transform::AbsoluteScale(EntityWrapper entity) +{ + return AbsoluteScale(entity.World, entity.ID); +} + glm::vec3 Transform::AbsoluteScale(World* world, EntityID entity) { glm::vec3 scale(1.f); @@ -40,6 +55,11 @@ glm::vec3 Transform::AbsoluteScale(World* world, EntityID entity) return scale; } +glm::mat4 Transform::ModelMatrix(EntityWrapper entity) +{ + return ModelMatrix(entity.ID, entity.World); +} + glm::mat4 Transform::ModelMatrix(EntityID entity, World* world) { glm::vec3 position = Transform::AbsolutePosition(world, entity); diff --git a/src/Engine/Editor/EditorSystem.cpp b/src/Engine/Editor/EditorSystem.cpp index 35b8a361..9c0e2e47 100644 --- a/src/Engine/Editor/EditorSystem.cpp +++ b/src/Engine/Editor/EditorSystem.cpp @@ -14,10 +14,11 @@ EditorSystem::EditorSystem(World* world, EventBroker* eventBroker, IRenderer* re m_EditorWorldSystemPipeline->AddSystem(0, m_Renderer); m_EditorWorldSystemPipeline->AddSystem(1, m_Renderer, m_RenderFrame); - m_Camera = importEntity(EntityWrapper(m_EditorWorld, EntityID_Invalid), "Schema/Entities/Empty.xml"); - m_EditorWorld->AttachComponent(m_Camera.ID, "Transform"); - m_EditorWorld->AttachComponent(m_Camera.ID, "Camera"); - m_DebugCameraInputController = new DebugCameraInputController(m_EventBroker, -1); + m_EditorCamera = importEntity(EntityWrapper(m_EditorWorld, EntityID_Invalid), "Schema/Entities/Empty.xml"); + m_ActualCamera = m_EditorCamera; + m_EditorWorld->AttachComponent(m_EditorCamera.ID, "Transform"); + m_EditorWorld->AttachComponent(m_EditorCamera.ID, "Camera"); + m_EditorCameraInputController = new EditorCameraInputController(m_EventBroker, -1); m_EditorGUI = new EditorGUI(m_World, m_EventBroker); m_EditorGUI->SetEntitySelectedCallback(std::bind(&EditorSystem::OnEntitySelected, this, std::placeholders::_1)); @@ -33,38 +34,70 @@ EditorSystem::EditorSystem(World* world, EventBroker* eventBroker, IRenderer* re EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &EditorSystem::OnMousePress); EVENT_SUBSCRIBE_MEMBER(m_EWidgetDelta, &EditorSystem::OnWidgetDelta); + EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &EditorSystem::OnInputCommand); + EVENT_SUBSCRIBE_MEMBER(m_ESetCamera, &EditorSystem::OnSetCamera); m_EditorStats = new EditorStats(); - Events::SetCamera e; - e.CameraEntity = m_Camera; - m_EventBroker->Publish(e); + if (m_Enabled) { + Enable(); + } } EditorSystem::~EditorSystem() { delete m_EditorStats; delete m_EditorGUI; - delete m_DebugCameraInputController; + delete m_EditorCameraInputController; delete m_EditorWorldSystemPipeline; delete m_EditorWorld; } void EditorSystem::Update(double dt) { - m_EventBroker->Process(); - m_EditorGUI->Draw(); - m_EditorStats->Draw(dt); + double now = glfwGetTime(); + double actualDelta = now - m_LastTime; + m_LastTime = now; - if (m_CurrentSelection.Valid() && m_Widget.Valid()) { - (glm::vec3&)m_Widget["Transform"]["Position"] = Transform::AbsolutePosition(m_CurrentSelection.World, m_CurrentSelection.ID); + if (m_Enabled) { + m_EventBroker->Process(); + m_EditorGUI->Draw(); + m_EditorStats->Draw(actualDelta); + + if (m_CurrentSelection.Valid() && m_Widget.Valid()) { + (glm::vec3&)m_Widget["Transform"]["Position"] = Transform::AbsolutePosition(m_CurrentSelection.World, m_CurrentSelection.ID); + } + + m_EditorWorldSystemPipeline->Update(actualDelta); + + ComponentWrapper& cameraTransform = m_EditorCamera["Transform"]; + glm::vec3& ori = cameraTransform["Orientation"]; + ori.x = m_EditorCameraInputController->Rotation().x; + ori.y = m_EditorCameraInputController->Rotation().y; + glm::vec3& pos = cameraTransform["Position"]; + pos += m_EditorCameraInputController->Movement() * glm::inverse(glm::quat(ori)) * (float)actualDelta; } +} - m_EditorWorldSystemPipeline->Update(dt); +void EditorSystem::Enable() +{ + m_EditorCameraInputController->Enable(); + m_EventBroker->Publish(Events::UnlockMouse()); + Events::SetCamera e; + e.CameraEntity = m_EditorCamera; + m_EventBroker->Publish(e); + (glm::vec3&)m_EditorCamera["Transform"]["Position"] = Transform::AbsolutePosition(m_ActualCamera); + m_Enabled = true; +} - m_DebugCameraInputController->Update(dt); - m_Camera["Transform"]["Position"] = m_DebugCameraInputController->Position(); - m_Camera["Transform"]["Orientation"] = glm::eulerAngles(m_DebugCameraInputController->Orientation()); +void EditorSystem::Disable() +{ + m_EditorCameraInputController->Disable(); + m_EventBroker->Publish(Events::LockMouse()); + Events::SetCamera e; + e.CameraEntity = m_ActualCamera; + m_EventBroker->Publish(e); + m_Enabled = false; } void EditorSystem::OnEntitySelected(EntityWrapper entity) @@ -148,6 +181,33 @@ bool EditorSystem::OnWidgetDelta(const Events::WidgetDelta& e) return true; } +bool EditorSystem::OnInputCommand(const Events::InputCommand& e) +{ + if (e.PlayerID != -1) { + return false; + } + + if (e.Command == "ToggleEditor" && e.Value > 0) { + if (m_Enabled) { + Disable(); + } else { + Enable(); + } + } + return true; +} + +bool EditorSystem::OnSetCamera(const Events::SetCamera& e) +{ + if (m_Enabled && e.CameraEntity != m_EditorCamera) { + m_ActualCamera = e.CameraEntity; + Events::SetCamera e2; + e2.CameraEntity = m_EditorCamera; + m_EventBroker->Publish(e2); + } + return true; +} + EntityWrapper EditorSystem::importEntity(EntityWrapper parent, boost::filesystem::path filePath) { if (parent.World == nullptr) { diff --git a/src/Engine/Network/Client.cpp b/src/Engine/Network/Client.cpp index 179d9b4a..49964128 100644 --- a/src/Engine/Network/Client.cpp +++ b/src/Engine/Network/Client.cpp @@ -5,14 +5,20 @@ using namespace boost::asio::ip; Client::Client(ConfigFile* config) : m_Socket(m_IOService) { + Network::initialize(); + // Asumes root node is EntityID 0 insertIntoServerClientMaps(0, 0); + // Init timer + m_TimeSinceSentInputs = std::clock(); // Default is local host std::string address = config->Get("Networking.Address", "127.0.0.1"); int port = config->Get("Networking.Port", 13); m_ReceiverEndpoint = udp::endpoint(boost::asio::ip::address::from_string(address), port); // Set up network stream m_PlayerName = config->Get("Networking.Name", "Raptorcopter"); + m_SendInputIntervalMs = config->Get("Networking.SendInputIntervalMs", 33); + } Client::~Client() @@ -37,19 +43,24 @@ void Client::Update() readFromServer(); if (m_IsConnected) { hasServerTimedOut(); + // Don't sent 1 input in 1 packet, bunch em up. + if (m_SendInputIntervalMs < (1000 * (std::clock() - m_TimeSinceSentInputs) / (double)CLOCKS_PER_SEC)) { + sendInputCommands(); + m_TimeSinceSentInputs = std::clock(); + } } + Network::Update(); } void Client::readFromServer() { while (m_Socket.available()) { - bytesRead = receive(readBuf, INPUTSIZE); + bytesRead = receive(readBuf); if (bytesRead > 0) { Packet packet(readBuf, bytesRead); parseMessageType(packet); } } - sendInputCommands(); } void Client::parseMessageType(Packet& packet) @@ -66,12 +77,9 @@ void Client::parseMessageType(Packet& packet) case MessageType::Connect: parseConnect(packet); break; - case MessageType::ClientPing: + case MessageType::Ping: parsePing(); break; - case MessageType::ServerPing: - parseServerPing(); - break; case MessageType::Message: break; case MessageType::Snapshot: @@ -81,6 +89,13 @@ void Client::parseMessageType(Packet& packet) break; case MessageType::PlayerConnected: parsePlayerConnected(packet); + break; + case MessageType::Kick: + parseKick(); + break; + case MessageType::OnPlayerSpawned: + parsePlayersSpawned(packet); + break; default: break; } @@ -99,11 +114,6 @@ void Client::parsePlayerConnected(Packet & packet) } void Client::parsePing() -{ - -} - -void Client::parseServerPing() { // Might miss connect message so set it here instead. m_IsConnected = true; @@ -112,11 +122,26 @@ void Client::parseServerPing() LOG_INFO("%i: response time with ctime(ms): %f", m_PacketID, m_DurationOfPingTime); m_StartPingTime = std::clock(); - Packet packet(MessageType::ServerPing, m_SendPacketID); + Packet packet(MessageType::Ping, m_SendPacketID); packet.WriteString("Ping recieved"); send(packet); } +void Client::parseKick() +{ + LOG_WARNING("You have been kicked from the server."); + m_IsConnected = false; +} + +void Client::parsePlayersSpawned(Packet& packet) +{ + Events::PlayerSpawned e; + e.Player = EntityWrapper(m_World, m_ServerIDToClientID[packet.ReadPrimitive()]); + e.Spawner = EntityWrapper(m_World, m_ServerIDToClientID[packet.ReadPrimitive()]); + e.PlayerID = -1; + m_EventBroker->Publish(e); +} + // Fields with strings will not work right now void Client::InterpolateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID, const std::string& componentType) { @@ -153,8 +178,12 @@ void Client::parseSnapshot(Packet& packet) { std::string componentType = packet.ReadString(); while (packet.DataReadSize() < packet.Size()) { + // HACK + std::string entityName = packet.ReadString(); // Components EntityID EntityID receivedEntityID = packet.ReadPrimitive(); + // HACK + m_World->SetName(receivedEntityID, entityName); // Parents EntityID EntityID receivedParentEntityID = packet.ReadPrimitive(); ComponentInfo componentInfo = m_World->GetComponents(componentType)->ComponentInfo(); @@ -211,15 +240,20 @@ void Client::parseSnapshot(Packet& packet) } } -int Client::receive(char* data, size_t length) +int Client::receive(char* data) { boost::system::error_code error; int bytesReceived = m_Socket.receive_from(boost - ::asio::buffer((void*)data, length), + ::asio::buffer((void*)data, INPUTSIZE), m_ReceiverEndpoint, 0, error); - + // Network Debug data + if (isReadingData) { + m_NetworkData.TotalDataReceived += bytesReceived; + m_NetworkData.DataReceivedThisInterval += bytesReceived; + m_NetworkData.AmountOfMessagesReceived++; + } if (error) { //LOG_ERROR("receive: %s", error.message().c_str()); } @@ -232,6 +266,12 @@ void Client::send(Packet& packet) packet.Data(), packet.Size()), m_ReceiverEndpoint, 0); + // Network Debug data + if (isReadingData) { + m_NetworkData.TotalDataSent += packet.Size(); + m_NetworkData.DataSentThisInterval += packet.Size(); + m_NetworkData.AmountOfMessagesSent++; + } } void Client::connect() @@ -250,14 +290,6 @@ void Client::disconnect() send(packet); } -void Client::ping() -{ - //Packet packet(MessageType::Connect, m_SendPacketID); - //packet.WriteString("Ping"); - //m_StartPingTime = std::clock(); - //send(packet); -} - bool Client::OnInputCommand(const Events::InputCommand & e) { if (e.Command == "ConnectToServer") { // Connect for now @@ -275,6 +307,15 @@ bool Client::OnInputCommand(const Events::InputCommand & e) if (e.Value > 0) { becomePlayer(); } + } else if (e.Command == "LogNetworkBandwidth") { + if (e.Value > 0) { + // Save to file if we no longer want to read data. + if (isReadingData) { + saveToFile(); + } + isReadingData = !isReadingData; + m_SaveDataTimer = std::clock(); + } } else { m_InputCommandBuffer.push_back(e); //LOG_DEBUG("Client::OnInputCommand: Command is %s. Value is %f. PlayerID is %i.", e.Command.c_str(), e.Value, e.PlayerID); @@ -305,7 +346,7 @@ bool Client::hasServerTimedOut() { // Time in ms float timeSincePing = 1000 * (std::clock() - m_StartPingTime) / static_cast(CLOCKS_PER_SEC); - if (timeSincePing > TIMEOUTMS) { + if (timeSincePing > m_TimeoutMs) { // Clear everything and go to menu. LOG_INFO("Server has timed out, returning to menu, Beep Boop."); m_IsConnected = false; diff --git a/src/Engine/Network/Network.cpp b/src/Engine/Network/Network.cpp new file mode 100644 index 00000000..f4dcd1a2 --- /dev/null +++ b/src/Engine/Network/Network.cpp @@ -0,0 +1,68 @@ +#include "Network/Network.h" + +void Network::Update() +{ + updateNetworkData(); +} + +void Network::saveToFile() +{ + std::ofstream outfile; + time_t t = time(0); + // get time now + struct tm * now = localtime(&t); + // Get current time and date + std::string dateAndTime = "BandwidthData - " + std::to_string(now->tm_year + 1900) + '-' + + std::to_string(now->tm_mon + 1) + '-' + + std::to_string(now->tm_mday) + '_' + + std::to_string(now->tm_hour) + "h." + + std::to_string(now->tm_min) + "m." + + std::to_string(now->tm_sec) + 's'; + + outfile.open(dateAndTime + ".csv"); + outfile << "Total time," + std::to_string(m_NetworkData.TotalTime) + "\n"; + outfile << "Total data received," + std::to_string(m_NetworkData.TotalDataReceived) + "\n"; + outfile << "Total data sent," + std::to_string(m_NetworkData.TotalDataSent) + "\n"; + outfile << "Total messages received," + std::to_string(m_NetworkData.AmountOfMessagesReceived) + "\n"; + outfile << "Total messages sent," + std::to_string(m_NetworkData.AmountOfMessagesSent) + "\n"; + + float messagesReceivedPerSec = (float)m_NetworkData.AmountOfMessagesReceived / (m_NetworkData.TotalTime / 1000); + float messagesSentPerSec = (float)m_NetworkData.AmountOfMessagesSent / (m_NetworkData.TotalTime / 1000); + float dataReceivedPerSec = (float)m_NetworkData.TotalDataReceived / (m_NetworkData.TotalTime / 1000); + float dataSentPerSec = (float)m_NetworkData.TotalDataSent / (m_NetworkData.TotalTime / 1000); + outfile << "Avarage messages received / s: " + std::to_string(messagesReceivedPerSec) + "\n"; + outfile << "Avarage messages sents / s: " + std::to_string(messagesSentPerSec) + "\n"; + outfile << "Avarage data received B/s: " + std::to_string(dataReceivedPerSec) + "\n"; + outfile << "Avarage data sents B/s: " + std::to_string(dataSentPerSec) + "\n"; + + outfile << "time, avg receive B, avg send B\n"; + for (int i = 0; i < m_NetworkData.BandwidthBytes.size(); i++) { + outfile << std::to_string(i) + ","; + outfile << std::to_string(m_NetworkData.BandwidthBytes[i].first) + ","; + outfile << std::to_string(m_NetworkData.BandwidthBytes[i].second) + "\n"; + } + outfile.close(); + +} + +void Network::updateNetworkData() +{ + std::clock_t currentTime = std::clock(); + // Send snapshot + if (m_SaveDataIntervalMs < (1000 * (currentTime - m_SaveDataTimer) / (double)CLOCKS_PER_SEC)) { + // Set values + m_NetworkData.TotalTime += (1000 * (currentTime - m_SaveDataTimer) / (double)CLOCKS_PER_SEC); + m_NetworkData.BandwidthBytes.push_back(std::pair(m_NetworkData.DataReceivedThisInterval, m_NetworkData.DataSentThisInterval)); + // Reset interval stuff + m_SaveDataTimer = std::clock(); + m_NetworkData.DataSentThisInterval = 0; + m_NetworkData.DataReceivedThisInterval = 0; + } +} + +void Network::initialize() +{ + ConfigFile* config = ResourceManager::Load("Config.ini"); + m_MaxConnections = config->Get("Networking.MaxConnections", 8); + m_TimeoutMs = config->Get("Networking.TimeoutMs", 20000); +} diff --git a/src/Engine/Network/Packet.cpp b/src/Engine/Network/Packet.cpp index 6308a130..d40a1b32 100644 --- a/src/Engine/Network/Packet.cpp +++ b/src/Engine/Network/Packet.cpp @@ -39,6 +39,7 @@ void Packet::Init(MessageType type, unsigned int & packetID) Packet::WritePrimitive(messageType); Packet::WritePrimitive(packetID); packetID++; + m_HeaderSize = m_Offset; } void Packet::WriteString(const std::string& str) diff --git a/src/Engine/Network/Server.cpp b/src/Engine/Network/Server.cpp index dd3f39c2..3ab3dd5f 100644 --- a/src/Engine/Network/Server.cpp +++ b/src/Engine/Network/Server.cpp @@ -1,7 +1,13 @@ #include "Network/Server.h" -Server::Server() : m_Socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 13)) -{ } +Server::Server() : m_Socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 27666)) +{ + Network::initialize(); + ConfigFile* config = ResourceManager::Load("Config.ini"); + snapshotInterval = 1000 * config->Get("Networking.SnapshotInterval", 0.05); + pingIntervalMs = config->Get("Networking.PingIntervalMs", 1000); + +} Server::~Server() { @@ -14,7 +20,8 @@ void Server::Start(World* world, EventBroker* eventBroker) m_EventBroker = eventBroker; // Subscribe to events EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &Server::OnInputCommand); - for (size_t i = 0; i < MAXCONNECTIONS; i++) { + EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &Server::OnPlayerSpawned); + for (size_t i = 0; i < m_MaxConnections; i++) { m_PlayerDefinitions[i].StopTime = std::clock(); } LOG_INFO("I am Server. BIP BOP\n"); @@ -24,14 +31,17 @@ void Server::Update() { readFromClients(); m_EventBroker->Process(); -} + if (isReadingData) { + Network::Update(); + } +} void Server::readFromClients() { while (m_Socket.available()) { try { - bytesRead = receive(readBuffer, INPUTSIZE); + bytesRead = receive(readBuffer); Packet packet(readBuffer, bytesRead); parseMessageType(packet); } catch (const std::exception& err) { @@ -46,7 +56,7 @@ void Server::readFromClients() } // Send pings each - if (intervalMs < (1000 * (currentTime - previousePingMessage) / (double)CLOCKS_PER_SEC)) { + if (pingIntervalMs < (1000 * (currentTime - previousePingMessage) / (double)CLOCKS_PER_SEC)) { sendPing(); previousePingMessage = currentTime; } @@ -70,11 +80,8 @@ void Server::parseMessageType(Packet& packet) case MessageType::Connect: parseConnect(packet); break; - case MessageType::ClientPing: - //parseClientPing(); - break; - case MessageType::ServerPing: - parseServerPing(); + case MessageType::Ping: + parsePing(); break; case MessageType::Message: break; @@ -97,21 +104,47 @@ void Server::parseMessageType(Packet& packet) } } -int Server::receive(char * data, size_t length) +int Server::receive(char * data) { - length = m_Socket.receive_from( + unsigned int length = m_Socket.receive_from( boost::asio::buffer((void*)data - , length) + , INPUTSIZE) , m_ReceiverEndpoint, 0); + // Network Debug data + if (isReadingData) { + m_NetworkData.TotalDataReceived += length; + m_NetworkData.DataReceivedThisInterval += length; + m_NetworkData.AmountOfMessagesReceived++; + } return length; } -void Server::send(Packet& packet, int userID) +void Server::send(Packet& packet, UserID user) { int bytesSent = m_Socket.send_to( boost::asio::buffer(packet.Data(), packet.Size()), - m_ConnectedUsers[userID].Endpoint, + m_ConnectedUsers[user].Endpoint, 0); + // Network Debug data + if (isReadingData) { + m_NetworkData.TotalDataSent += packet.Size(); + m_NetworkData.DataSentThisInterval += packet.Size(); + m_NetworkData.AmountOfMessagesSent++; + } +} + +void Server::send(PlayerID player, Packet& packet) +{ + int bytesSent = m_Socket.send_to( + boost::asio::buffer(packet.Data(), packet.Size()), + m_PlayerDefinitions[player].Endpoint, + 0); + // Network Debug data + if (isReadingData) { + m_NetworkData.TotalDataSent += packet.Size(); + m_NetworkData.DataSentThisInterval += packet.Size(); + m_NetworkData.AmountOfMessagesSent++; + } } void Server::send(Packet & packet) @@ -122,6 +155,11 @@ void Server::send(Packet & packet) packet.Size()), m_ReceiverEndpoint, 0); + if (isReadingData) { + // Network Debug data + m_NetworkData.TotalDataSent += packet.Size(); + m_NetworkData.DataSentThisInterval += packet.Size(); + } } void Server::broadcast(Packet& packet) @@ -143,9 +181,12 @@ void Server::sendSnapshot() Packet packet(MessageType::Snapshot); ComponentPool* componentPool = it.second; ComponentInfo componentInfo = componentPool->ComponentInfo(); + // Component Type packet.WriteString(componentInfo.Name); for (auto& componentWrapper : *componentPool) { + // HACK: Send entity name + packet.WriteString(m_World->GetName(componentWrapper.EntityID)); // Components EntityID packet.WritePrimitive(componentWrapper.EntityID); // Parents EntityID @@ -160,7 +201,9 @@ void Server::sendSnapshot() } } } - broadcast(packet); + if (packet.Size() > packet.HeaderSize() + componentInfo.Name.size()) { + broadcast(packet); + } } } @@ -174,7 +217,7 @@ void Server::sendPing() } } // Create ping message - Packet packet(MessageType::ServerPing); + Packet packet(MessageType::Ping); packet.WriteString("Ping from server"); // Time message m_StartPingTime = std::clock(); @@ -191,7 +234,7 @@ void Server::checkForTimeOuts() if (m_ConnectedUsers[i].Endpoint.address() != boost::asio::ip::address()) { int stopPing = 1000 * m_ConnectedUsers[i].StopTime / static_cast(CLOCKS_PER_SEC); - if (startPing > stopPing + TIMEOUTMS) { + if (startPing > stopPing + m_TimeoutMs) { LOG_INFO("User %i timed out!", i); disconnect(i); } @@ -199,35 +242,40 @@ void Server::checkForTimeOuts() } } -void Server::disconnect(int i) +void Server::disconnect(UserID user) { //broadcast("A player disconnected"); - LOG_INFO("User %s disconnected/timed out", m_PlayerDefinitions[i].Name.c_str()); + LOG_INFO("User %s disconnected/timed out", m_PlayerDefinitions[user].Name.c_str()); // Remove enteties and stuff (When we can remove entity, remove it and tell clients to remove the copy they have) - m_PlayerDefinitions[i].Endpoint = boost::asio::ip::udp::endpoint(); - m_PlayerDefinitions[i].EntityID = -1; - m_PlayerDefinitions[i].Name = ""; - m_PlayerDefinitions[i].PacketID = 0; - m_ConnectedUsers.erase(m_ConnectedUsers.begin() + i); + Events::PlayerDisconnected e; + e.Entity = m_PlayerDefinitions[user].EntityID; + e.PlayerID = user; + m_EventBroker->Publish(e); + + m_PlayerDefinitions[user].Endpoint = boost::asio::ip::udp::endpoint(); + m_PlayerDefinitions[user].EntityID = -1; + m_PlayerDefinitions[user].Name = ""; + m_PlayerDefinitions[user].PacketID = 0; + m_ConnectedUsers.erase(m_ConnectedUsers.begin() + user); } void Server::parseOnInputCommand(Packet& packet) { - int playerID = -1; + PlayerID player = -1; // Check which player it was who sent the message - for (int i = 0; i < MAXCONNECTIONS; i++) { + for (int i = 0; i < m_MaxConnections; i++) { // if the player is connected set playerID to the correct PlayerID if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address() && m_PlayerDefinitions[i].Endpoint.port() == m_ReceiverEndpoint.port()) { - playerID = i; + player = i; break; } } - if (playerID != -1) { + if (player != -1) { while (packet.DataReadSize() < packet.Size()) { Events::InputCommand e; e.Command = packet.ReadString(); - e.PlayerID = playerID; // Set correct player id + e.PlayerID = player; // Set correct player id e.Value = packet.ReadPrimitive(); m_EventBroker->Publish(e); //LOG_INFO("Server::parseOnInputCommand: Command is %s. Value is %f. PlayerID is %i.", e.Command.c_str(), e.Value, e.PlayerID); @@ -292,17 +340,17 @@ void Server::parseDisconnect() void Server::parseClientPing() { LOG_INFO("%i: Parsing ping", m_PacketID); - int playerID = GetPlayerIDFromEndpoint(m_ReceiverEndpoint); - if (playerID == -1) { + PlayerID player = GetPlayerIDFromEndpoint(m_ReceiverEndpoint); + if (player == -1) { return; } // Return ping - Packet packet(MessageType::ClientPing, m_PlayerDefinitions[playerID].PacketID); + Packet packet(MessageType::Ping, m_PlayerDefinitions[player].PacketID); packet.WriteString("Ping received"); send(packet); } -void Server::parseServerPing() +void Server::parsePing() { for (int i = 0; i < m_ConnectedUsers.size(); i++) { if (m_ConnectedUsers[i].Endpoint.address() == m_ReceiverEndpoint.address()) { @@ -328,7 +376,7 @@ void Server::createPlayer() LOG_WARNING("Already connected!"); return; } - int userIndex; + UserID userIndex; for (userIndex = 0; userIndex < m_ConnectedUsers.size(); userIndex++) { if (m_ConnectedUsers[userIndex].Endpoint.address() == m_ReceiverEndpoint.address() && m_ConnectedUsers[userIndex].Endpoint.port() == m_ReceiverEndpoint.port()) { @@ -340,7 +388,7 @@ void Server::createPlayer() LOG_WARNING("Not a recognized user!"); return; } - for (int playerIndex = 0; playerIndex < MAXCONNECTIONS; playerIndex++) { + for (PlayerID playerIndex = 0; playerIndex < m_MaxConnections; playerIndex++) { if (m_PlayerDefinitions[playerIndex].Endpoint.address() == boost::asio::ip::address()) { m_PlayerDefinitions[playerIndex] = m_ConnectedUsers[userIndex]; EntityID entityID = m_World->CreateEntity(); @@ -355,12 +403,19 @@ void Server::createPlayer() } } LOG_WARNING("Server is full!"); - + } -int Server::GetPlayerIDFromEndpoint(boost::asio::ip::udp::endpoint endpoint) +void Server::kick(PlayerID player) { - for (int i = 0; i < MAXCONNECTIONS; i++) { + disconnect(player); + Packet packet = Packet(MessageType::Kick); + send(packet); +} + +PlayerID Server::GetPlayerIDFromEndpoint(boost::asio::ip::udp::endpoint endpoint) +{ + for (int i = 0; i < m_MaxConnections; i++) { if (m_PlayerDefinitions[i].Endpoint.address() == endpoint.address() && m_PlayerDefinitions[i].Endpoint.port() == endpoint.port()) { return i; @@ -372,5 +427,25 @@ int Server::GetPlayerIDFromEndpoint(boost::asio::ip::udp::endpoint endpoint) bool Server::OnInputCommand(const Events::InputCommand & e) { //LOG_DEBUG("Server::OnInputCommand: Command is %s. Value is %f. PlayerID is %i.", e.Command.c_str(), e.Value, e.PlayerID); + if (e.Command == "LogNetworkBandwidth" && e.Value > 0) { + if (isReadingData) { + saveToFile(); + } + isReadingData = !isReadingData; + m_SaveDataTimer = std::clock(); + } + if (e.Command == "KickPlayer" && e.Value > 0) { + kick(0); + } + return true; } + +bool Server::OnPlayerSpawned(const Events::PlayerSpawned & e) +{ + Packet packet = Packet(MessageType::OnPlayerSpawned); + packet.WritePrimitive(e.Player.ID); + packet.WritePrimitive(e.Spawner.ID); + send(e.PlayerID, packet); + return false; +} diff --git a/src/Engine/Rendering/RenderSystem.cpp b/src/Engine/Rendering/RenderSystem.cpp index 334b3b1b..9496fe7a 100644 --- a/src/Engine/Rendering/RenderSystem.cpp +++ b/src/Engine/Rendering/RenderSystem.cpp @@ -7,6 +7,7 @@ RenderSystem::RenderSystem(World* world, EventBroker* eventBroker, const IRender { EVENT_SUBSCRIBE_MEMBER(m_ESetCamera, &RenderSystem::OnSetCamera); EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &RenderSystem::OnInputCommand); + EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &RenderSystem::OnPlayerSpawned); m_Camera = new Camera((float)m_Renderer->Resolution().Width / m_Renderer->Resolution().Height, glm::radians(45.f), 0.01f, 5000.f); } @@ -46,6 +47,13 @@ void RenderSystem::fillModels(std::list>& jobs) continue; } + EntityWrapper entity(m_World, modelComponent.EntityID); + + // Don't render the local player + if (entity == m_LocalPlayer || entity.IsChildOf(m_LocalPlayer)) { + continue; + } + Model* model; try { model = ResourceManager::Load<::Model, true>(resource); @@ -68,6 +76,14 @@ void RenderSystem::fillModels(std::list>& jobs) } } +bool RenderSystem::OnPlayerSpawned(Events::PlayerSpawned& e) +{ + if (e.PlayerID == -1) { + m_LocalPlayer = e.Player; + } + return true; +} + void RenderSystem::fillPointLights(std::list>& jobs, World* world) { auto pointLights = m_World->GetComponents("PointLight"); @@ -155,11 +171,12 @@ void RenderSystem::Update(double dt) { m_EventBroker->Process(); - if (m_CurrentCamera) { - ComponentWrapper cameraTransform = m_CurrentCamera["Transform"]; - m_Camera->SetPosition(cameraTransform["Position"]); - m_Camera->SetOrientation(glm::quat((const glm::vec3&)cameraTransform["Orientation"])); + // Update the current camera used for rendering + if (m_CurrentCamera.Valid()) { + m_Camera->SetPosition(Transform::AbsolutePosition(m_CurrentCamera)); + m_Camera->SetOrientation(Transform::AbsoluteOrientation(m_CurrentCamera)); } + //Only supports opaque geometry atm RenderScene scene; diff --git a/src/Game/Systems/InterpolationSystem.cpp b/src/Game/Systems/InterpolationSystem.cpp index 75f68678..2b6e82f2 100644 --- a/src/Game/Systems/InterpolationSystem.cpp +++ b/src/Game/Systems/InterpolationSystem.cpp @@ -1,24 +1,14 @@ #include "Systems/InterpolationSystem.h" -//void InterpolationSystem::UpdateComponent(World * world, ComponentWrapper & transform, double dt) -//{ -// if (m_InterpolationPoints[transform.EntityID].size() > 0) { -// Transform& sTransform = m_InterpolationPoints[transform.EntityID].front(); -// sTransform.interpolationTime += dt; -// if (sTransform.interpolationTime > 0.05) { -// double time = std::fmod(sTransform.interpolationTime, 0.05f); -// m_InterpolationPoints[transform.EntityID].pop(); -// if (m_InterpolationPoints[transform.EntityID].size() <= 0) { -// return; -// } -// sTransform = m_InterpolationPoints[transform.EntityID].front(); -// sTransform.interpolationTime = time; -// } -// glm::vec3 nextPosition = sTransform.Position; -// glm::vec3 currentPosition = static_cast(transform["Position"]); -// transform["Position"] = vectorInterpolation(currentPosition, nextPosition, sTransform.interpolationTime); -// } -//} +InterpolationSystem::InterpolationSystem(World* world, EventBroker* eventBroker) + : System(world, eventBroker) + , PureSystem("Transform") +{ + ConfigFile* config = ResourceManager::Load("Config.ini"); + m_SnapshotInterval = config->Get("Networking.SnapshotInterval", 0.05); + EVENT_SUBSCRIBE_MEMBER(m_EInterpolate, &InterpolationSystem::OnInterpolate); + EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &InterpolationSystem::OnPlayerSpawned); +} void InterpolationSystem::UpdateComponent(EntityWrapper& entity, ComponentWrapper& transform, double dt) { @@ -26,10 +16,10 @@ void InterpolationSystem::UpdateComponent(EntityWrapper& entity, ComponentWrappe m_NextTransform[transform.EntityID].interpolationTime += dt; Transform sTransform = m_NextTransform[transform.EntityID]; double time = sTransform.interpolationTime; - if (time > SNAPSHOTINTERVAL) { + if (time > m_SnapshotInterval) { if (m_LastReceivedTransform.find(transform.EntityID) != m_LastReceivedTransform.end()) { m_NextTransform[transform.EntityID] = m_LastReceivedTransform[transform.EntityID]; - m_NextTransform[transform.EntityID].interpolationTime = time - SNAPSHOTINTERVAL; + m_NextTransform[transform.EntityID].interpolationTime = time - m_SnapshotInterval; sTransform = m_NextTransform[transform.EntityID]; m_LastReceivedTransform.erase(transform.EntityID); } else { @@ -37,14 +27,22 @@ void InterpolationSystem::UpdateComponent(EntityWrapper& entity, ComponentWrappe } } if (transform.Info.Name == "Transform") { + bool isLocalPlayer = entity == m_LocalPlayer || entity.IsChildOf(m_LocalPlayer); // Position glm::vec3 nextPosition = sTransform.Position; glm::vec3 currentPosition = static_cast(transform["Position"]); + // HACK: Hardcoded tolerance value for player position desync = 1 + if (isLocalPlayer && glm::length(nextPosition - currentPosition) < 1.f) { + return; + } (glm::vec3&)transform["Position"] += vectorInterpolation(currentPosition, nextPosition, sTransform.interpolationTime); // Orientation - glm::quat nextOrientation = sTransform.Orientation; - glm::quat currentOrientation = glm::quat(static_cast(transform["Orientation"])); - (glm::vec3&)transform["Orientation"] = glm::eulerAngles(glm::slerp(currentOrientation, nextOrientation, sTransform.interpolationTime / SNAPSHOTINTERVAL)); + // Don't force orientation for players + if (!isLocalPlayer) { + glm::quat nextOrientation = sTransform.Orientation; + glm::quat currentOrientation = glm::quat(static_cast(transform["Orientation"])); + (glm::vec3&)transform["Orientation"] = glm::eulerAngles(glm::slerp(currentOrientation, nextOrientation, sTransform.interpolationTime / m_SnapshotInterval)); + } // Scale glm::vec3 nextScale = sTransform.Scale; glm::vec3 currentScale = static_cast(transform["Scale"]); @@ -53,6 +51,12 @@ void InterpolationSystem::UpdateComponent(EntityWrapper& entity, ComponentWrappe } } +bool InterpolationSystem::OnPlayerSpawned(Events::PlayerSpawned& e) +{ + m_LocalPlayer = e.Player; + return true; +} + bool InterpolationSystem::OnInterpolate(const Events::Interpolate & e) { Transform transform; @@ -72,15 +76,5 @@ bool InterpolationSystem::OnInterpolate(const Events::Interpolate & e) } else { // Did not m_NextTransform[e.Entity] = transform; } - // Check if queue already exists - //if (m_InterpolationPoints.find(e.Entity) != m_InterpolationPoints.end()) { // Did exist, push to queue - // m_InterpolationPoints[e.Entity].push(transform); - //} - - //else { // Did not exist, create queue - // std::queue transformQueue; - // transformQueue.push(transform); - // m_InterpolationPoints[e.Entity] = transformQueue; - //} return false; } diff --git a/src/Game/Systems/PlayerMovementSystem.cpp b/src/Game/Systems/PlayerMovementSystem.cpp index e14cd146..5c75a673 100644 --- a/src/Game/Systems/PlayerMovementSystem.cpp +++ b/src/Game/Systems/PlayerMovementSystem.cpp @@ -1,18 +1,132 @@ #include "Systems/PlayerMovementSystem.h" +PlayerMovementSystem::PlayerMovementSystem(World* world, EventBroker* eventBroker) + : System(world, eventBroker) + , PureSystem("Player") +{ + EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &PlayerMovementSystem::OnPlayerSpawned); +} + +PlayerMovementSystem::~PlayerMovementSystem() +{ + for (auto& kv : m_PlayerInputControllers) { + delete kv.second; + } +} + +void PlayerMovementSystem::Update(double dt) +{ + for (auto& kv : m_PlayerInputControllers) { + EntityWrapper player = kv.first; + auto& controller = kv.second; + + if (!player.Valid()) { + continue; + } + + EntityWrapper cameraEntity = player.FirstChildByName("Camera"); + if (cameraEntity.Valid()) { + glm::vec3& cameraOrientation = cameraEntity["Transform"]["Orientation"]; + cameraOrientation.x += controller->Rotation().x; + // Limit camera pitch so we don't break our necks + cameraOrientation.x = glm::clamp(cameraOrientation.x, -glm::half_pi(), glm::half_pi()); + } + + ComponentWrapper& cTransform = player["Transform"]; + glm::vec3& ori = cTransform["Orientation"]; + ori.y += controller->Rotation().y; + + if (player.HasComponent("Physics")) { + ComponentWrapper cPhysics = player["Physics"]; + + glm::vec3 wishDirection = controller->Movement() * glm::inverse(glm::quat(ori)); + float wishSpeed; + if (controller->Crouching()) { + wishSpeed = player["Player"]["CrouchSpeed"]; + } else { + wishSpeed = player["Player"]["MovementSpeed"]; + } + glm::vec3& velocity = cPhysics["Velocity"]; + ImGui::Text("velocity: (%f, %f, %f)", velocity.x, velocity.y, velocity.z); + glm::vec3 groundVelocity(0.f, 0.f, 0.f); + groundVelocity.x = glm::dot(velocity, glm::vec3(1.f, 0.f, 0.f)); + groundVelocity.z = glm::dot(velocity, glm::vec3(0.f, 0.f, 1.f)); + ImGui::Text("groundVelocity: (%f, %f, %f) |%f|", groundVelocity.x, groundVelocity.y, groundVelocity.z, glm::length(wishDirection)); + ImGui::Text("wishDirection: (%f, %f, %f) |%f|", wishDirection.x, wishDirection.y, wishDirection.z, glm::length(wishDirection)); + float currentSpeedProj = glm::dot(groundVelocity, wishDirection); + float addSpeed = wishSpeed - currentSpeedProj; + ImGui::Text("currentSpeedProj: %f", currentSpeedProj); + ImGui::Text("wishSpeed: %f", wishSpeed); + ImGui::Text("addSpeed: %f", addSpeed); + + if (addSpeed > 0) { + static float accel = 15.f; + ImGui::InputFloat("accel", &accel); + static float airAccel = 0.5f; + ImGui::InputFloat("airAccel", &airAccel); + float actualAccel = (velocity.y != 0) ? airAccel : accel; + static float surfaceFriction = 5.f; + ImGui::InputFloat("surfaceFriction", &surfaceFriction); + float accelerationSpeed = actualAccel * (float)dt * wishSpeed * surfaceFriction; + accelerationSpeed = glm::min(accelerationSpeed, addSpeed); + velocity += accelerationSpeed * wishDirection; + ImGui::Text("velocity: (%f, %f, %f) |%f|", velocity.x, velocity.y, velocity.z, glm::length(velocity)); + } + + if (controller->Jumping() && !controller->Crouching() && velocity.y == 0.f) { + velocity.y += 4.f; + } + + //if (player.HasComponent("AABB")) { + // glm::vec3& size = player["AABB"]["Size"]; + // if (controller->Crouching()) { + // size = glm::vec3(1.f, 1.f, 1.f); + // } else { + // size = glm::vec3(1.f, 1.6f, 1.f); + // } + //} + } + + controller->Reset(); + } +} + void PlayerMovementSystem::UpdateComponent(EntityWrapper& entity, ComponentWrapper& component, double dt) { ComponentWrapper& cTransform = entity["Transform"]; if (!entity.HasComponent("Physics")) { return; } - ComponentWrapper& cPhysics = entity["Physics"]; + ComponentWrapper& cPhysics = entity["Physics"]; glm::vec3& velocity = cPhysics["Velocity"]; + + // Ground friction + float speed = glm::length(velocity); + static float groundFriction = 7.f; + ImGui::InputFloat("groundFriction", &groundFriction); + static float airFriction = 0.f; + ImGui::InputFloat("airFriction", &airFriction); + float friction = (velocity.y != 0) ? airFriction : groundFriction; + if (speed > 0) { + float drop = speed * friction * (float)dt; + float multiplier = glm::max(speed - drop, 0.f) / speed; + velocity.x *= multiplier; + velocity.z *= multiplier; + } + if (cPhysics["Gravity"]) { - velocity.y -= 9.82 * dt; + velocity.y -= 9.82f * (float)dt; } glm::vec3& position = cTransform["Position"]; position += velocity * (float)dt; -} \ No newline at end of file +} + +bool PlayerMovementSystem::OnPlayerSpawned(Events::PlayerSpawned& e) +{ + // When a player spawns, create an input controller for them + m_PlayerInputControllers[e.Player] = new FirstPersonInputController(m_EventBroker, e.PlayerID); + + return true; +} diff --git a/src/Game/Systems/PlayerSpawnSystem.cpp b/src/Game/Systems/PlayerSpawnSystem.cpp index 4224d46c..84dce05c 100644 --- a/src/Game/Systems/PlayerSpawnSystem.cpp +++ b/src/Game/Systems/PlayerSpawnSystem.cpp @@ -4,6 +4,8 @@ PlayerSpawnSystem::PlayerSpawnSystem(World* m_World, EventBroker* eventBroker) : System(m_World, eventBroker) { EVENT_SUBSCRIBE_MEMBER(m_OnInputCommand, &PlayerSpawnSystem::OnInputCommand); + EVENT_SUBSCRIBE_MEMBER(m_OnPlayerSpawnerd, &PlayerSpawnSystem::OnPlayerSpawned); + m_NetworkEnabled = ResourceManager::Load("Config.ini")->Get("Networking.StartNetwork", false); } void PlayerSpawnSystem::Update(double dt) @@ -13,7 +15,7 @@ void PlayerSpawnSystem::Update(double dt) return; } - for (auto& team : m_SpawnRequests) { + for (auto& req : m_SpawnRequests) { for (auto& cPlayerSpawn : *playerSpawns) { EntityWrapper spawner(m_World, cPlayerSpawn.EntityID); if (!spawner.HasComponent("Spawner")) { @@ -22,7 +24,7 @@ void PlayerSpawnSystem::Update(double dt) // If the spawner has a team affiliation, check it if (spawner.HasComponent("Team")) { - if ((int)spawner["Team"]["Team"] != team) { + if ((int)spawner["Team"]["Team"] != req.Team) { continue; } } @@ -30,7 +32,15 @@ void PlayerSpawnSystem::Update(double dt) // Spawn the player! EntityWrapper player = SpawnerSystem::Spawn(spawner); // Set the player team affiliation - player["Team"]["Team"] = team; + player["Team"]["Team"] = req.Team; + + // Publish a PlayerSpawned event + Events::PlayerSpawned e; + e.PlayerID = req.PlayerID; + e.Player = player; + e.Spawner = spawner; + m_EventBroker->Publish(e); + } } m_SpawnRequests.clear(); @@ -42,10 +52,60 @@ bool PlayerSpawnSystem::OnInputCommand(const Events::InputCommand& e) return false; } + // Team picks should be processed ONLY server-side! + // Don't make a spawn request if PlayerID is -1, i.e. we're the client. + if (e.PlayerID == -1 && m_NetworkEnabled) { + return false; + } + if (e.Value != 0) { - m_SpawnRequests.push_back((int)e.Value); + SpawnRequest req; + req.PlayerID = e.PlayerID; + req.Team = (ComponentInfo::EnumType)e.Value; + m_SpawnRequests.push_back(req); } return true; } +bool PlayerSpawnSystem::OnPlayerSpawned(Events::PlayerSpawned& e) +{ + // When a player is actually spawned (since the actual spawning is handled on the server) + + // Check if a player already exists + if (m_PlayerEntities.count(e.PlayerID) != 0) { + // TODO: Disallow infinite respawning here + m_World->DeleteEntity(m_PlayerEntities[e.PlayerID].ID); + } + + // Store the player for future reference + m_PlayerEntities[e.PlayerID] = e.Player; + + // Set the camera to the correct entity + EntityWrapper cameraEntity = e.Player.FirstChildByName("Camera"); + if (cameraEntity.Valid()) { + Events::SetCamera e; + e.CameraEntity = cameraEntity; + m_EventBroker->Publish(e); + } + + // HACK: Set the player model color to team color + EntityWrapper playerModel = e.Player.FirstChildByName("PlayerModel"); + if (playerModel.Valid() && e.Player.HasComponent("Team")) { + ComponentWrapper cTeam = e.Player["Team"]; + ComponentWrapper cModel = playerModel["Model"]; + if ((ComponentInfo::EnumType)cTeam["Team"] == cTeam["Team"].Enum("Red")) { + cModel["Color"] = glm::vec3(1.f, 0.f, 0.f); + } else if ((ComponentInfo::EnumType)cTeam["Team"] == cTeam["Team"].Enum("Blue")) { + cModel["Color"] = glm::vec3(0.f, 0.25f, 1.f); + } + } + + // TODO: Set the player name to whatever + //EntityWrapper playerName = e.Player.FirstChildByName("PlayerName"); + //if (playerName.Valid()) { + // playerName["Text"]["Content"] = ???; + //} + + return true; +} \ No newline at end of file