diff --git a/RoboCat/Assets/ARIBL0.ttf b/RoboCat/Assets/ARIBL0.ttf new file mode 100644 index 00000000..a1e68a36 Binary files /dev/null and b/RoboCat/Assets/ARIBL0.ttf differ diff --git a/RoboCat/Assets/Background_Image.jpg b/RoboCat/Assets/Background_Image.jpg new file mode 100644 index 00000000..dc0ef63d Binary files /dev/null and b/RoboCat/Assets/Background_Image.jpg differ diff --git a/RoboCat/Assets/Player_Image.png b/RoboCat/Assets/Player_Image.png new file mode 100644 index 00000000..c8f237fc Binary files /dev/null and b/RoboCat/Assets/Player_Image.png differ diff --git a/RoboCat/Assets/Rock_Image.png b/RoboCat/Assets/Rock_Image.png new file mode 100644 index 00000000..f2322dae Binary files /dev/null and b/RoboCat/Assets/Rock_Image.png differ diff --git a/RoboCat/Assets/Square_Image.png b/RoboCat/Assets/Square_Image.png new file mode 100644 index 00000000..c679d476 Binary files /dev/null and b/RoboCat/Assets/Square_Image.png differ diff --git a/RoboCat/Chapter3.vcxproj b/RoboCat/Chapter3.vcxproj index 8c859a81..cabc7347 100644 --- a/RoboCat/Chapter3.vcxproj +++ b/RoboCat/Chapter3.vcxproj @@ -92,6 +92,11 @@ true true Bin\$(Configuration)\ + true + true + true + true + true true @@ -128,7 +133,7 @@ ProgramDatabase EnableFastChecks ..\SDL\include;Inc;..\ - Use + NotUsing RoboCatPCH.h @@ -370,14 +375,24 @@ + + + + + + + + + + @@ -390,15 +405,21 @@ + + + + + + diff --git a/RoboCat/Inc/AckRange.h b/RoboCat/Inc/AckRange.h index 004d14c9..d1432c4d 100644 --- a/RoboCat/Inc/AckRange.h +++ b/RoboCat/Inc/AckRange.h @@ -1,3 +1,5 @@ +#pragma once + //typedef PacketSequenceNumber uint16_t; class AckRange diff --git a/RoboCat/Inc/Colour.h b/RoboCat/Inc/Colour.h new file mode 100644 index 00000000..5400c78d --- /dev/null +++ b/RoboCat/Inc/Colour.h @@ -0,0 +1,43 @@ +#pragma once + +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +Colour.h + File information: + This file contains data used for colours. +*/ + +class Colour +{ + //-------------------------Private data------------------------- + + //Red channel + unsigned __int8 mR; + + //Green channel + unsigned __int8 mG; + + //Blue channel + unsigned __int8 mB; + + //Alpha channel + unsigned __int8 mA; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + Colour(); + Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b); + Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b, unsigned __int8 a); + + //Destructor + ~Colour(); + + //Accessor(s) + unsigned __int8 getR() { return mR; }; + unsigned __int8 getG() { return mG; }; + unsigned __int8 getB() { return mB; }; + unsigned __int8 getA() { return mA; }; +}; \ No newline at end of file diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index b6a795a4..2622106f 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,52 +1,77 @@ +#pragma once + +#include "MemoryBitStream.h" +#include "InFlightPacket.h" +#include "AckRange.h" +#include +#include + +using std::deque; + +typedef uint16_t PacketSequenceNumber; + +////https://www.geeksforgeeks.org/priority-queue-of-pairs-in-c-with-ordering-by-first-and-second-element/ +//struct myComp { +// constexpr bool operator()( +// std::pair const& a, +// std::pair const& b) +// const noexcept +// { +// return a.first.GetSequenceNumber() > b.first.GetSequenceNumber(); +// } +//}; + +class Networker; class DeliveryNotificationManager { public: - - DeliveryNotificationManager( bool inShouldSendAcks, bool inShouldProcessAcks ); + DeliveryNotificationManager(bool inShouldSendAcks, bool inShouldProcessAcks, Networker* pNetworker); ~DeliveryNotificationManager(); - inline InFlightPacket* WriteState( OutputMemoryBitStream& inOutputStream ); - inline bool ReadAndProcessState( InputMemoryBitStream& inInputStream ); + inline InFlightPacket* WriteState(OutputMemoryBitStream& inOutputStream); + inline bool ReadAndProcessState(InputMemoryBitStream& inInputStream); - void ProcessTimedOutPackets(); + void ResendPacket(const int ID, const PacketSequenceNumber packetSequenceNum); + void ProcessTimedOutPackets(); - uint32_t GetDroppedPacketCount() const { return mDroppedPacketCount; } - uint32_t GetDeliveredPacketCount() const { return mDeliveredPacketCount; } - uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } + uint32_t GetDroppedPacketCount() const { return mDroppedPacketCount; } + uint32_t GetDeliveredPacketCount() const { return mDeliveredPacketCount; } + uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } - const deque< InFlightPacket >& GetInFlightPackets() const { return mInFlightPackets; } + //const deque& GetInFlightPackets() const { return mInFlightPackets; } + const deque> GetInFlightPackets() const { return mInFlightPacketsPair; } private: - - - - InFlightPacket* WriteSequenceNumber( OutputMemoryBitStream& inOutputStream ); - void WriteAckData( OutputMemoryBitStream& inOutputStream ); + + InFlightPacket* WriteSequenceNumber(OutputMemoryBitStream& inOutputStream); + void WriteAckData(OutputMemoryBitStream& inOutputStream); //returns wether to drop the packet- if sequence number is too low! - bool ProcessSequenceNumber( InputMemoryBitStream& inInputStream ); - void ProcessAcks( InputMemoryBitStream& inInputStream ); - + bool ProcessSequenceNumber(InputMemoryBitStream& inInputStream); + void ProcessAcks(InputMemoryBitStream& inInputStream); - void AddPendingAck( PacketSequenceNumber inSequenceNumber ); - void HandlePacketDeliveryFailure( const InFlightPacket& inFlightPacket ); - void HandlePacketDeliverySuccess( const InFlightPacket& inFlightPacket ); + void AddPendingAck(PacketSequenceNumber inSequenceNumber); + void HandlePacketDeliveryFailure(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum); + void HandlePacketDeliverySuccess(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum); - PacketSequenceNumber mNextOutgoingSequenceNumber; - PacketSequenceNumber mNextExpectedSequenceNumber; + PacketSequenceNumber mNextOutgoingSequenceNumber; + PacketSequenceNumber mNextExpectedSequenceNumber; - deque< InFlightPacket > mInFlightPackets; - deque< AckRange > mPendingAcks; + //deque mInFlightPackets; + deque> mInFlightPacketsPair; + deque mPendingAcks; - bool mShouldSendAcks; - bool mShouldProcessAcks; + bool mShouldSendAcks; + bool mShouldProcessAcks; - uint32_t mDeliveredPacketCount; - uint32_t mDroppedPacketCount; - uint32_t mDispatchedPacketCount; + uint32_t mDeliveredPacketCount; + uint32_t mDroppedPacketCount; + uint32_t mDispatchedPacketCount; + //Networker + Networker* mpNetworker; }; diff --git a/RoboCat/Inc/GameObject.h b/RoboCat/Inc/GameObject.h new file mode 100644 index 00000000..ef412ed8 --- /dev/null +++ b/RoboCat/Inc/GameObject.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include "GraphicsLibrary.h" +#include "TransmissionData.h" + +using std::pair; + +/* + File information: + This file contains the definition for the GameObject class. +*/ + +enum GameObjectType +{ + INVALID = -1, + ROCK, + WALL, + PLAYER, + ENUM_SIZE +}; + +class GameObject : public TransmissionData +{ + //-------------------------Private data------------------------- + + + //-------------------------Protected data------------------------- + +protected: + + //Identifiers + const GameObjectType mGameObjectType = GameObjectType::INVALID; + const int mNetworkID; + + //Position + pair mPosition; + + //Graphics library + GraphicsLibrary* pGraphicsLibrary; + + //Sprite identifier + const std::string mSPRITE_IDENTIFIER; + + //Constructor(s) + GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary); + GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier = ""); + + //-------------------------Public data------------------------- +public: + + //Copy constructor + GameObject(const GameObject& copy); + + //Destructor + virtual ~GameObject(); + + //Accessor(s) + const GameObjectType getGameObjectType() { return mGameObjectType; }; + const int getNetworkID() { return mNetworkID; }; + const pair getPosition() { return mPosition; }; + + //Mutator(s) + float setPosX(float posX) { mPosition.first = posX; }; + float setPosY(float posY) { mPosition.second = posY; }; + void setPos(pair newPos) { mPosition = newPos; }; + + //Functions + virtual void update() = 0; + virtual void draw() = 0; +}; \ No newline at end of file diff --git a/RoboCat/Inc/GraphicsLibrary.h b/RoboCat/Inc/GraphicsLibrary.h new file mode 100644 index 00000000..32ccafcc --- /dev/null +++ b/RoboCat/Inc/GraphicsLibrary.h @@ -0,0 +1,80 @@ +#pragma once + +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +GraphicsLibrary.h + File information: + This file contains function abstractions from Allegro 5, wrapped up in my Graphics Library. This will + be used to render images and text to the screen. + Source I am consulting: Allegro 5.0.10 Manual - http://cdn.allegro.cc/file/library/allegro/5.0.10/allegro-5.0.10-manual.pdf +*/ + +#include +#include + +#include "Colour.h" + +//https://github.com/liballeg/allegro_wiki/wiki/Allegro-in-Visual-Studio#using-nuget-within-visual-studio +#include +#include +#include +#include +#include +#include + +enum TextAlignment +{ + ALIGN_LEFT = ALLEGRO_ALIGN_LEFT, + ALIGN_CENTER = ALLEGRO_ALIGN_CENTRE, + ALIGN_RIGHT = ALLEGRO_ALIGN_RIGHT +}; + +class GraphicsLibrary +{ + //-------------------------Private data------------------------- + + //Screen data + float mScreenSizeX; + float mScreenSizeY; + + //Allegro display + ALLEGRO_DISPLAY* mpDisplay; + + //Other images to draw + std::vector> mBitmapPointersVector; + + //UI text + ALLEGRO_FONT* mpFont; + ALLEGRO_COLOR mTextColour; + + friend class InputSystem; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + GraphicsLibrary(float screenSizeX, float screenSizeY); + + //Destructor + ~GraphicsLibrary(); + + //Accessor(s) + float getScreenSizeX() { return mScreenSizeX; }; + float getScreenSizeY() { return mScreenSizeY; }; + + //Mutator(s) + + //Functions + bool init(); + bool initText(std::string fontFilePath, int fontSize, Colour textColour); + void render(); + void loadImage(std::string imageFilePath, std::string imageIdentifier); + + //Drawing functions + void drawText(float posX, float posY, std::string text, TextAlignment alignment); + void drawRectangle(float topLeftX, float topLeftY, float bottomRightX, float bottomRightY, Colour colour, float thickness); + void drawImage(std::string imageIdentifier, float posX, float posY); + void drawScaledImage(std::string imageIdentifier, float posX, float posY, float scaleX, float scaleY); + void drawTintedImage(std::string imageIdentifier, float posX, float posY, Colour col); +}; \ No newline at end of file diff --git a/RoboCat/Inc/InFlightPacket.h b/RoboCat/Inc/InFlightPacket.h index 494e097e..9e620e3a 100644 --- a/RoboCat/Inc/InFlightPacket.h +++ b/RoboCat/Inc/InFlightPacket.h @@ -1,3 +1,9 @@ +#pragma once + +#include + +using std::unordered_map; + class DeliveryNotificationManager; //in case we decide to change the type of the sequence number to use fewer or more bits @@ -12,22 +18,22 @@ class InFlightPacket PacketSequenceNumber GetSequenceNumber() const { return mSequenceNumber; } float GetTimeDispatched() const { return mTimeDispatched; } - void SetTransmissionData( int inKey, TransmissionDataPtr inTransmissionData ) + void SetTransmissionData( int inKey, TransmissionData* inTransmissionData ) { mTransmissionDataMap[ inKey ] = inTransmissionData; } - const TransmissionDataPtr GetTransmissionData( int inKey ) const + const TransmissionData* GetTransmissionData( int inKey ) const { auto it = mTransmissionDataMap.find( inKey ); return ( it != mTransmissionDataMap.end() ) ? it->second : nullptr; } - void HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const; - void HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const; + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; private: PacketSequenceNumber mSequenceNumber; float mTimeDispatched; - unordered_map< int, TransmissionDataPtr > mTransmissionDataMap; + unordered_map< int, TransmissionData* > mTransmissionDataMap; }; \ No newline at end of file diff --git a/RoboCat/Inc/InputSystem.h b/RoboCat/Inc/InputSystem.h new file mode 100644 index 00000000..eb9ec329 --- /dev/null +++ b/RoboCat/Inc/InputSystem.h @@ -0,0 +1,75 @@ +#pragma once + +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +InputSystem.h + File information: + This file contains the keycodes for input, which can be used in any way desired by other classes + and files. +*/ + +#include "GraphicsLibrary.h" + +//Include allegro libraries for input +#include + +enum KeyCode +{ + Esc = ALLEGRO_KEY_ESCAPE, + R = ALLEGRO_KEY_R, + Tab = ALLEGRO_KEY_TAB, + W = ALLEGRO_KEY_W, + A = ALLEGRO_KEY_A, + S = ALLEGRO_KEY_S, + D = ALLEGRO_KEY_D, + BACK = ALLEGRO_KEY_BACKSPACE +}; + +enum MouseButton +{ + LeftMouse = 0, + RightMouse = 1, + MiddleMouse = 2 +}; + +enum InputMode +{ + NONE = -1, + KeyPressed = ALLEGRO_EVENT_KEY_DOWN, + KeyReleased = ALLEGRO_EVENT_KEY_UP, + MouseDown = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, + MouseUp = ALLEGRO_EVENT_MOUSE_BUTTON_UP +}; + +class InputSystem +{ + //-------------------------Private data------------------------- + + //Event queue + ALLEGRO_EVENT_QUEUE* mpMouseEventQueue; + ALLEGRO_EVENT_QUEUE* mpKeyboardEventQueue; + + //Event + ALLEGRO_EVENT mMouseEvent; + ALLEGRO_EVENT mKeyboardEvent; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + InputSystem(); + + //Destructor + ~InputSystem(); + + //Accessor(s) + float getMouseX(); + float getMouseY(); + std::pair getMousePosition(); + + //Functions + bool init(GraphicsLibrary* pGraphicsLib); + MouseButton getMouseInput(InputMode inputMode); + KeyCode getKeyboardInput(InputMode inputMode); +}; \ No newline at end of file diff --git a/RoboCat/Inc/MemoryBitStream.h b/RoboCat/Inc/MemoryBitStream.h index c2138925..ba65a8ff 100644 --- a/RoboCat/Inc/MemoryBitStream.h +++ b/RoboCat/Inc/MemoryBitStream.h @@ -1,8 +1,10 @@ +#pragma once #include #include #include #include +#include "RoboMath.h" class GameObject; diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h new file mode 100644 index 00000000..41caed9b --- /dev/null +++ b/RoboCat/Inc/Networker.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "GameObject.h" +#include "Rock.h" +#include "Wall.h" +#include "PlayerController.h" +#include "DeliveryNotificationManager.h" +#include "RoboCatPCH.h" + +//https://www.geeksforgeeks.org/priority-queue-of-pairs-in-c-with-ordering-by-first-and-second-element/ +struct myComp { + constexpr bool operator()( + std::pair const& a, + std::pair const& b) + const noexcept + { + return a.first < b.first; //Sort by LEAST, since we send the queue.top and pop that first + } +}; + +enum PacketType +{ + PACKET_HELLO, + PACKET_CREATE, + PACKET_UPDATE, + PACKET_DELETE, + PACKET_INVALID +}; + +//Networker is singleton (we only want one networker at a time) +class Networker +{ +public: + + static Networker* GetInstance() + { + if (mInstance) + { + delete mInstance; + mInstance = nullptr; + } + + mInstance = new Networker; + return mInstance; + }; + + void init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour/*, float arrivalTime*/); + void cleanup(); + + ~Networker(); + + //Starting and connect to server + bool initServerUDP(std::string clientIpAddress, std::string servPort, std::string clientPort); + bool connectUDP(std::string serverIpAddress, std::string servPort, std::string clientPort); + + //Update game state - UDP + PacketType receiveGameObjectStateUDP(); + void sendGameObjectStateUDP(int networkID, PacketType packetHeader); + void checkTimedOutPackets(); + + //Map + void addGameObject(/*GameObject**/ shared_ptr objectToAdd, int networkID); + /*GameObject**/ shared_ptr getGameObject(int networkID) { return mGameObjectsVec[networkID].second; }; + void updateGameObjects(); + void renderGameObjects(); + +private: + + Networker(); + static Networker* mInstance; + + //Data + UDPSocketPtr* mpUDPSocket; + SocketAddressPtr* mpSocketAddressPtr; + std::vector>> mGameObjectsVec; + DeliveryNotificationManager* pDeliveryNotificationManager; + std::priority_queue, std::vector>, myComp> mOutputBitStreamQueue; + bool mbIsInitted; + + //Data for GameObject replication + GraphicsLibrary* mpGraphicsLibrary; + float mPlayerMoveSpeed; + std::string mRockSpriteIdentifier; + std::string mPlayerSpriteIdentifier; + Colour mWallColour; +}; \ No newline at end of file diff --git a/RoboCat/Inc/OutputWindow.h b/RoboCat/Inc/OutputWindow.h index a12ad9ef..73312118 100644 --- a/RoboCat/Inc/OutputWindow.h +++ b/RoboCat/Inc/OutputWindow.h @@ -1,3 +1,4 @@ +#pragma once // Encapsulates a simulated "output window," where // new messages are written from top to bottom diff --git a/RoboCat/Inc/PlayerController.h b/RoboCat/Inc/PlayerController.h new file mode 100644 index 00000000..722a18f4 --- /dev/null +++ b/RoboCat/Inc/PlayerController.h @@ -0,0 +1,51 @@ +#pragma once + +/* + File information: + This file contains the definition for the PlayerController class, derived from GameObject +*/ + +#include "GameObject.h" +#include "InputSystem.h" +//#include "TransmissionData.h" + +class PlayerController : public GameObject/*, public TransmissionData*/ +{ + //-------------------------Private data------------------------- + + //Reference to input system + InputSystem* pInputKeyDown; + InputSystem* pInputKeyUp; + + bool bShouldMoveUp = false; + bool bShouldMoveDown = false; + bool bShouldMoveLeft = false; + bool bShouldMoveRight = false; + + //Movement data + float mMoveSpeed; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary); + PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float moveSpeed, const std::string spriteIdentifier); + + //Overloaded functions + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + + //Destructor + ~PlayerController(); + + //Accessor(s) + float getMoveSpeed() { return mMoveSpeed; }; + + //Mutator(s) + void setMoveSpeed(float moveSpeed) { mMoveSpeed = moveSpeed; }; + + //Functions + void update(); + void draw(); +}; \ No newline at end of file diff --git a/RoboCat/Inc/RoboMath.h b/RoboCat/Inc/RoboMath.h index 718080a9..80603dad 100644 --- a/RoboCat/Inc/RoboMath.h +++ b/RoboCat/Inc/RoboMath.h @@ -1,3 +1,5 @@ +#pragma once + class Vector3 { public: diff --git a/RoboCat/Inc/Rock.h b/RoboCat/Inc/Rock.h new file mode 100644 index 00000000..dbf79f8a --- /dev/null +++ b/RoboCat/Inc/Rock.h @@ -0,0 +1,39 @@ +#pragma once + +/* + File information: + This file contains the definition for the Rock class, derived from GameObject +*/ + +#include "GameObject.h" +//#include "TransmissionData.h" + +class Rock : public GameObject/*, public TransmissionData*/ +{ + //-------------------------Private data------------------------- + + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + Rock(const int networkID, GraphicsLibrary* graphicsLibrary); + Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier); + + //Overloaded functions + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + + //Destructor + ~Rock(); + + //Accessor(s) + + + //Mutator(s) + + + //Functions + void update(); + void draw(); +}; \ No newline at end of file diff --git a/RoboCat/Inc/SocketAddress.h b/RoboCat/Inc/SocketAddress.h index 05bad2b8..0c8d5671 100644 --- a/RoboCat/Inc/SocketAddress.h +++ b/RoboCat/Inc/SocketAddress.h @@ -1,3 +1,5 @@ +#pragma once + class SocketAddress { public: diff --git a/RoboCat/Inc/SocketAddressFactory.h b/RoboCat/Inc/SocketAddressFactory.h index fe1e0516..2f0bfe67 100644 --- a/RoboCat/Inc/SocketAddressFactory.h +++ b/RoboCat/Inc/SocketAddressFactory.h @@ -1,4 +1,4 @@ - +#pragma once class SocketAddressFactory { diff --git a/RoboCat/Inc/SocketUtil.h b/RoboCat/Inc/SocketUtil.h index 50a726c3..7238c192 100644 --- a/RoboCat/Inc/SocketUtil.h +++ b/RoboCat/Inc/SocketUtil.h @@ -1,3 +1,5 @@ +#pragma once + enum class SocketAddressFamily { INET = AF_INET, diff --git a/RoboCat/Inc/TCPSocket.h b/RoboCat/Inc/TCPSocket.h index 0779a82b..42d8fb58 100644 --- a/RoboCat/Inc/TCPSocket.h +++ b/RoboCat/Inc/TCPSocket.h @@ -1,3 +1,5 @@ +#pragma once + class TCPSocket { public: @@ -9,6 +11,7 @@ class TCPSocket int32_t Send( const void* inData, size_t inLen ); int32_t Receive( void* inBuffer, size_t inLen ); int SetNonBlockingMode(bool inShouldBeNonBlocking); + void CleanupSocket(); private: friend class SocketUtil; TCPSocket( SOCKET inSocket ) : mSocket( inSocket ) {} diff --git a/RoboCat/Inc/Timing.h b/RoboCat/Inc/Timing.h index b5af28d8..23edd9fe 100644 --- a/RoboCat/Inc/Timing.h +++ b/RoboCat/Inc/Timing.h @@ -1,3 +1,5 @@ +#pragma once + class Timing { public: diff --git a/RoboCat/Inc/TransmissionData.h b/RoboCat/Inc/TransmissionData.h index cc599ec0..8d695fdb 100644 --- a/RoboCat/Inc/TransmissionData.h +++ b/RoboCat/Inc/TransmissionData.h @@ -1,9 +1,16 @@ +#pragma once + +#include +using std::shared_ptr; + +typedef uint16_t PacketSequenceNumber; + class DeliveryNotificationManager; class TransmissionData { public: - virtual void HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const = 0; - virtual void HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const = 0; + virtual void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const = 0; + virtual void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const = 0; }; typedef shared_ptr< TransmissionData > TransmissionDataPtr; \ No newline at end of file diff --git a/RoboCat/Inc/UDPSocket.h b/RoboCat/Inc/UDPSocket.h index 939df407..6ed44431 100644 --- a/RoboCat/Inc/UDPSocket.h +++ b/RoboCat/Inc/UDPSocket.h @@ -1,4 +1,4 @@ - +#pragma once class UDPSocket { @@ -16,6 +16,7 @@ class UDPSocket */ int SetNonBlockingMode( bool inShouldBeNonBlocking ); + void CleanupSocket(); private: friend class SocketUtil; diff --git a/RoboCat/Inc/Wall.h b/RoboCat/Inc/Wall.h new file mode 100644 index 00000000..5c7771c4 --- /dev/null +++ b/RoboCat/Inc/Wall.h @@ -0,0 +1,48 @@ +#pragma once + +/* + File information: + This file contains the definition for the Wall class, derived from GameObject +*/ + +#include "GameObject.h" +#include "Colour.h" +//#include "TransmissionData.h" + +class Wall : public GameObject/*, public TransmissionData*/ +{ + //-------------------------Private data------------------------- + float mSizeX; + float mSizeY; + float mThickness; + + Colour mColour; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + Wall(const int networkID, GraphicsLibrary* graphicsLibrary); + Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float sizeX, float sizeY, Colour colour, float thickness); + + //Overloaded functions + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + + //Destructor + ~Wall(); + + //Accessor(s) + float getWallSizeX() { return mSizeX; }; + float getWallSizeY() { return mSizeY; }; + float getWallThickness() { return mThickness; }; + + //Mutator(s) + void setWallSizeX(float sizeX) { mSizeX = sizeX; }; + void setWallSizeY(float sizeY) { mSizeY = sizeY; }; + void setWallThickness(float thiccc) { mThickness = thiccc; }; + + //Functions + void update(); + void draw(); +}; \ No newline at end of file diff --git a/RoboCat/Src/AckRange.cpp b/RoboCat/Src/AckRange.cpp index 99bed803..3e05b1f3 100644 --- a/RoboCat/Src/AckRange.cpp +++ b/RoboCat/Src/AckRange.cpp @@ -1,4 +1,5 @@ #include "RoboCatPCH.h" +#include "AckRange.h" void AckRange::Write( OutputMemoryBitStream& inOutputStream ) const { diff --git a/RoboCat/Src/Colour.cpp b/RoboCat/Src/Colour.cpp new file mode 100644 index 00000000..f4877e0f --- /dev/null +++ b/RoboCat/Src/Colour.cpp @@ -0,0 +1,41 @@ +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +Colour.cpp +*/ + +#include "RoboCatPCH.h" +#include "Colour.h" + +//Base constructor +Colour::Colour() +{ + mR = 255; + mG = 255; + mB = 255; + mA = 255; +} + +//Constructor - without alpha +Colour::Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b) +{ + mR = r; + mG = g; + mB = b; + mA = 255; +} + +//Constructor - with alpha +Colour::Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b, unsigned __int8 a) +{ + mR = r; + mG = g; + mB = b; + mA = a; +} + +//Destructor +Colour::~Colour() +{ + +} \ No newline at end of file diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index ea85baa9..a31dc055 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -1,11 +1,12 @@ #include "RoboCatPCH.h" +#include "Networker.h" namespace { const float kDelayBeforeAckTimeout = 0.5f; } -DeliveryNotificationManager::DeliveryNotificationManager( bool inShouldSendAcks, bool inShouldProcessAcks ) : +DeliveryNotificationManager::DeliveryNotificationManager(bool inShouldSendAcks, bool inShouldProcessAcks, Networker* pNetworker) : mNextOutgoingSequenceNumber( 0 ), mNextExpectedSequenceNumber( 0 ), //everybody starts at 0... @@ -13,14 +14,18 @@ mShouldSendAcks( inShouldSendAcks ), mShouldProcessAcks( inShouldProcessAcks ), mDeliveredPacketCount( 0 ), mDroppedPacketCount( 0 ), -mDispatchedPacketCount( 0 ) +mDispatchedPacketCount( 0 ), +mpNetworker(pNetworker) { + mInFlightPacketsPair = deque>(); } //we're going away- log how well we did... DeliveryNotificationManager::~DeliveryNotificationManager() { + //Cleanup networker pointer (DO NOT DELETE, HAPPENS ELSEWHERE) + mpNetworker = nullptr; LOG( "DNM destructor. Delivery rate %d%%, Drop rate %d%%", ( 100 * mDeliveredPacketCount ) / mDispatchedPacketCount, ( 100 * mDroppedPacketCount ) / mDispatchedPacketCount ); @@ -38,9 +43,11 @@ InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBi if( mShouldProcessAcks ) { - mInFlightPackets.emplace_back( sequenceNumber ); + //mInFlightPackets.emplace_back( sequenceNumber ); + mInFlightPacketsPair.emplace_back( std::make_pair(sequenceNumber, &inOutputStream) ); - return &mInFlightPackets.back(); + //return &mInFlightPackets.back(); + return &mInFlightPacketsPair.back().first; } else { @@ -48,7 +55,7 @@ InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBi } } -void DeliveryNotificationManager::WriteAckData( OutputMemoryBitStream& inOutputStream ) +void DeliveryNotificationManager::WriteAckData(OutputMemoryBitStream& inOutputStream) { //we usually will only have one packet to ack //so we'll follow that with a 0 bit if that's the case @@ -127,26 +134,31 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre AckRange ackRange; ackRange.Read( inInputStream ); - //for each InfilghtPacket with a sequence number less than the start, handle delivery failure... + //for each InflightPacket with a sequence number less than the start, handle delivery failure... PacketSequenceNumber nextAckdSequenceNumber = ackRange.GetStart(); uint32_t onePastAckdSequenceNumber = nextAckdSequenceNumber + ackRange.GetCount(); - while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPackets.empty() ) + //while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPackets.empty() ) + while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPacketsPair.empty() ) { - const auto& nextInFlightPacket = mInFlightPackets.front(); + //const auto& nextInFlightPacket = mInFlightPackets.front(); + const auto& nextInFlightPacket = mInFlightPacketsPair.front().first; //if the packet has a lower sequence number, we didn't get an ack for it, so it probably wasn't delivered PacketSequenceNumber nextInFlightPacketSequenceNumber = nextInFlightPacket.GetSequenceNumber(); if( nextInFlightPacketSequenceNumber < nextAckdSequenceNumber ) { + HandlePacketDeliveryFailure(nextInFlightPacket, nextInFlightPacketSequenceNumber); + //copy this so we can remove it before handling the failure- we don't want to find it when checking for state auto copyOfInFlightPacket = nextInFlightPacket; - mInFlightPackets.pop_front(); - HandlePacketDeliveryFailure( copyOfInFlightPacket ); + //mInFlightPackets.pop_front(); + mInFlightPacketsPair.pop_front(); } else if( nextInFlightPacketSequenceNumber == nextAckdSequenceNumber ) { - HandlePacketDeliverySuccess( nextInFlightPacket ); + HandlePacketDeliverySuccess(nextInFlightPacket, nextInFlightPacketSequenceNumber); //received! - mInFlightPackets.pop_front(); + //mInFlightPackets.pop_front(); + mInFlightPacketsPair.pop_front(); //decrement count, advance nextAckdSequenceNumber ++nextAckdSequenceNumber; } @@ -160,20 +172,64 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre } } +void DeliveryNotificationManager::ResendPacket(const int ID, const PacketSequenceNumber packetSequenceNum) +{ + //Find packet header type + PacketType packetHeader; + //for (auto& it = mInFlightPackets.begin(); it != mInFlightPackets.end(); ++it) + for (auto& it = mInFlightPacketsPair.begin(); it != mInFlightPacketsPair.end(); ++it) + { + //If we found the inflight packet we want to re-send + if (it->first.GetSequenceNumber() == packetSequenceNum) + { + //Start reading packet until you find the PacketType + InputMemoryBitStream IMBStream = InputMemoryBitStream((char*)it->second->GetBufferPtr(), it->second->GetByteLength()); + + //Read junk data: 1) sequenceNumber, if shouldProcessAcks, 2) hasAcks, if hasAcks, 3) readAckRange + PacketSequenceNumber sequenceNumber; + IMBStream.Read(sequenceNumber); + + if (mShouldProcessAcks) + { + bool hasAcks; + IMBStream.Read(hasAcks); + + if (hasAcks) + { + AckRange ackRange; + ackRange.Read(IMBStream); + } + } + + //THEN we can read the rest of our packet, starting with PacketType + PacketType packetHeader; + IMBStream.Read(packetHeader); + mpNetworker->sendGameObjectStateUDP(ID, packetHeader); + return; + } + } + + //Error cout if it fails + std::cout << "ERROR: COULD NOT RESEND PACKET (DeliveryNotificationManager::ResendPacket()\n"; +} + void DeliveryNotificationManager::ProcessTimedOutPackets() { float timeoutTime = Timing::sInstance.GetTimef() - kDelayBeforeAckTimeout; - while( !mInFlightPackets.empty() ) + //while( !mInFlightPackets.empty() ) + while( !mInFlightPacketsPair.empty() ) { - const auto& nextInFlightPacket = mInFlightPackets.front(); + //const auto& nextInFlightPacket = mInFlightPackets.front(); + const auto& nextInFlightPacket = mInFlightPacketsPair.front().first; //was this packet dispatched before the current time minus the timeout duration? if( nextInFlightPacket.GetTimeDispatched() < timeoutTime ) { //it failed! let us know about that - HandlePacketDeliveryFailure( nextInFlightPacket ); - mInFlightPackets.pop_front(); + HandlePacketDeliveryFailure(nextInFlightPacket, nextInFlightPacket.GetSequenceNumber()); + //mInFlightPackets.pop_front(); + mInFlightPacketsPair.pop_front(); } else { @@ -194,16 +250,16 @@ void DeliveryNotificationManager::AddPendingAck( PacketSequenceNumber inSequence } -void DeliveryNotificationManager::HandlePacketDeliveryFailure( const InFlightPacket& inFlightPacket ) +void DeliveryNotificationManager::HandlePacketDeliveryFailure(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum) { ++mDroppedPacketCount; - inFlightPacket.HandleDeliveryFailure( this ); + inFlightPacket.HandleDeliveryFailure(this, packetSequenceNum); } -void DeliveryNotificationManager::HandlePacketDeliverySuccess( const InFlightPacket& inFlightPacket ) +void DeliveryNotificationManager::HandlePacketDeliverySuccess(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum) { ++mDeliveredPacketCount; - inFlightPacket.HandleDeliverySuccess( this ); + inFlightPacket.HandleDeliverySuccess(this, packetSequenceNum); } diff --git a/RoboCat/Src/GameObject.cpp b/RoboCat/Src/GameObject.cpp new file mode 100644 index 00000000..44e7a2ce --- /dev/null +++ b/RoboCat/Src/GameObject.cpp @@ -0,0 +1,39 @@ +#include "GameObject.h" +#include "RoboCatPCH.h" + +GameObject::GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary) + : mGameObjectType(gameObjectType), mNetworkID(networkID) +{ + //Graphics library + pGraphicsLibrary = graphicsLibrary; + + //Position + mPosition.first = 0.0; + mPosition.second = 0.0; +} + +GameObject::GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier) + : mGameObjectType(gameObjectType), mNetworkID(networkID), mSPRITE_IDENTIFIER(spriteIdentifier) +{ + //Graphics library + pGraphicsLibrary = graphicsLibrary; + + //Position + mPosition = position; +} + +GameObject::GameObject(const GameObject& copy) + : mGameObjectType(copy.mGameObjectType), mNetworkID(copy.mNetworkID), mSPRITE_IDENTIFIER(copy.mSPRITE_IDENTIFIER) +{ + //Graphics library + pGraphicsLibrary = copy.pGraphicsLibrary; + + //Position + mPosition = copy.mPosition; +} + +GameObject::~GameObject() +{ + //Graphics library gets cleaned up in main.cpp, just de-reference it here + pGraphicsLibrary = nullptr; +} \ No newline at end of file diff --git a/RoboCat/Src/GraphicsLibrary.cpp b/RoboCat/Src/GraphicsLibrary.cpp new file mode 100644 index 00000000..36d636de --- /dev/null +++ b/RoboCat/Src/GraphicsLibrary.cpp @@ -0,0 +1,173 @@ +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +GraphicsLibrary.cpp +*/ + +#include "GraphicsLibrary.h" +#include "RoboCatPCH.h" + +#include +#include + +//Constructor +GraphicsLibrary::GraphicsLibrary(float screenSizeX, float screenSizeY) +{ + //Setup data - screen size + mScreenSizeX = screenSizeX; + mScreenSizeY = screenSizeY; + + //Allegro display + mpDisplay = nullptr; +} + +//Destructor +GraphicsLibrary::~GraphicsLibrary() +{ + //Delete bitmaps + std::vector>::iterator iterator; + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + al_destroy_bitmap(iterator->second); + } + mBitmapPointersVector.clear(); + + //Clean up font + al_destroy_font(mpFont); + mpFont = nullptr; + + //Clean up display + al_destroy_display(mpDisplay); + mpDisplay = nullptr; +} + +bool GraphicsLibrary::init() +{ + //Init allegro + if (!al_init()) + { + std::cout << "error initting Allegro\n"; + system("pause"); + return false; + } + + //Init image addon + if (!al_init_image_addon()) + { + std::cout << "error initting image add-on\n"; + system("pause"); + return false; + } + + //Setup display + mpDisplay = al_create_display(mScreenSizeX, mScreenSizeY); + + if (mpDisplay == nullptr) + { + return false; + } + + return true; +} + +bool GraphicsLibrary::initText(std::string fontFilePath, int fontSize, Colour textColour) +{ + //Init font add on + if (!al_init_font_addon()) + { + std::cout << "error initting font add-on\n"; + system("pause"); + return false; + } + + //Init ttf add on + if (!al_init_ttf_addon()) + { + std::cout << "error initting ttf add-on\n"; + system("pause"); + return false; + } + + //Init primitives + if (!al_init_primitives_addon()) + { + std::cout << "error initting primitives add-on\n"; + system("pause"); + return false; + } + + //Init font + mpFont = al_load_ttf_font(fontFilePath.c_str(), fontSize, 0); + mTextColour = al_map_rgba(textColour.getR(), textColour.getG(), textColour.getB(), textColour.getA()); + + return true; +} + +void GraphicsLibrary::render() +{ + //Flip display buffers + al_flip_display(); +} + +void GraphicsLibrary::loadImage(std::string imageFilePath, std::string imageIdentifier) +{ + //Add the name of the image and the loaded bitmap to the vector of pairs + mBitmapPointersVector.push_back(std::make_pair(imageIdentifier, al_load_bitmap(imageFilePath.c_str()))); +} + +void GraphicsLibrary::drawText(float posX, float posY, std::string text, TextAlignment alignment) +{ + al_draw_text(mpFont, mTextColour, posX, posY, alignment, text.c_str()); +} + +void GraphicsLibrary::drawRectangle(float topLeftX, float topLeftY, float bottomRightX, float bottomRightY, Colour colour, float thickness) +{ + //Set colour + ALLEGRO_COLOR col = al_map_rgba(colour.getR(), colour.getG(), colour.getB(), colour.getA()); + al_draw_rectangle(topLeftX, topLeftY, bottomRightX, bottomRightY, col, thickness); +} + +void GraphicsLibrary::drawImage(std::string imageIdentifier, float posX, float posY) +{ + //Find the image and draw if it exists + std::vector>::iterator iterator; + + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + if (iterator->first == imageIdentifier) + { + al_draw_bitmap(iterator->second, posX, posY, 0); + } + } +} + +void GraphicsLibrary::drawScaledImage(std::string imageIdentifier, float posX, float posY, float scaleX, float scaleY) +{ + //Find the image and draw if it exists + std::vector>::iterator iterator; + + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + if (iterator->first == imageIdentifier) + { + al_draw_scaled_bitmap(iterator->second, 0, 0, al_get_bitmap_width(iterator->second), al_get_bitmap_height(iterator->second), posX, posY, al_get_bitmap_width(iterator->second) * scaleX, al_get_bitmap_height(iterator->second) * scaleY, 0); + } + } +} + +void GraphicsLibrary::drawTintedImage(std::string imageIdentifier, float posX, float posY, Colour col) +{ + //Find the image and draw if it exists + std::vector>::iterator iterator; + + //Set colour + ALLEGRO_COLOR colour = al_map_rgba(col.getR(), col.getG(), col.getB(), col.getA()); + + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + if (iterator->first == imageIdentifier) + { + al_draw_tinted_bitmap(iterator->second, colour, posX, posY, 0); + } + } +} \ No newline at end of file diff --git a/RoboCat/Src/InFlightPacket.cpp b/RoboCat/Src/InFlightPacket.cpp index 251abe36..13d4fc67 100644 --- a/RoboCat/Src/InFlightPacket.cpp +++ b/RoboCat/Src/InFlightPacket.cpp @@ -1,4 +1,5 @@ #include "RoboCatPCH.h" +#include "InFlightPacket.h" InFlightPacket::InFlightPacket( PacketSequenceNumber inSequenceNumber ) : mSequenceNumber( inSequenceNumber ), @@ -8,18 +9,22 @@ mTimeDispatched( Timing::sInstance.GetTimef() ) } -void InFlightPacket::HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const +void InFlightPacket::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { - for( const auto& pair : mTransmissionDataMap ) + //for( const auto& pair : mTransmissionDataMap ) + for (auto pair = mTransmissionDataMap.begin(); pair != mTransmissionDataMap.end(); pair++) { - pair.second->HandleDeliveryFailure( inDeliveryNotificationManager ); + //pair.second->HandleDeliveryFailure(inDeliveryNotificationManager, packetSequenceNum); + pair->second->HandleDeliveryFailure(inDeliveryNotificationManager, packetSequenceNum); } } -void InFlightPacket::HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const +void InFlightPacket::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { - for( const auto& pair : mTransmissionDataMap ) + //for( const auto& pair : mTransmissionDataMap ) + for (auto pair = mTransmissionDataMap.begin(); pair != mTransmissionDataMap.end(); pair++) { - pair.second->HandleDeliverySuccess( inDeliveryNotificationManager ); + //pair.second->HandleDeliverySuccess(inDeliveryNotificationManager, packetSequenceNum); + pair->second->HandleDeliverySuccess(inDeliveryNotificationManager, packetSequenceNum); } } \ No newline at end of file diff --git a/RoboCat/Src/InputSystem.cpp b/RoboCat/Src/InputSystem.cpp new file mode 100644 index 00000000..80a65846 --- /dev/null +++ b/RoboCat/Src/InputSystem.cpp @@ -0,0 +1,126 @@ +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +InputSystem.cpp +*/ + +#include "InputSystem.h" +#include "RoboCatPCH.h" + +#include + +//Constructor +InputSystem::InputSystem() +{ + //Create an event queue + mpMouseEventQueue = al_create_event_queue(); + mpKeyboardEventQueue = al_create_event_queue(); +} + +//Destructor +InputSystem::~InputSystem() +{ + //Cleanup event queues + al_destroy_event_queue(mpMouseEventQueue); + mpMouseEventQueue = nullptr; + al_destroy_event_queue(mpKeyboardEventQueue); + mpKeyboardEventQueue = nullptr; +} + +float InputSystem::getMouseX() +{ + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + return mouseState.x; +} + +float InputSystem::getMouseY() +{ + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + return mouseState.y; +} + +std::pair InputSystem::getMousePosition() +{ + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + return std::make_pair(mouseState.x, mouseState.y); +} + +//Init +bool InputSystem::init(GraphicsLibrary* pGraphicsLib) +{ + //Init keyboard + if (!al_install_keyboard()) + { + std::cout << "error installing Allegro keyboard plugin\n"; + system("pause"); + return false; + } + + //Init mouse + if (!al_install_mouse()) + { + std::cout << "error installing Allegro mouse plugin\n"; + system("pause"); + return false; + } + + //Register screen event source + al_register_event_source(mpMouseEventQueue, al_get_display_event_source(pGraphicsLib->mpDisplay)); + al_register_event_source(mpKeyboardEventQueue, al_get_display_event_source(pGraphicsLib->mpDisplay)); + + //Register mouse event source + al_register_event_source(mpMouseEventQueue, al_get_mouse_event_source()); + + //Register keyboard event source + al_register_event_source(mpKeyboardEventQueue, al_get_keyboard_event_source()); + + return true; +} + +MouseButton InputSystem::getMouseInput(InputMode inputMode) +{ + //If there is an event + al_get_next_event(mpMouseEventQueue, &mMouseEvent); + + if (mMouseEvent.type == inputMode) + { + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + //Check the button pressed + if (mouseState.buttons & 1) //Left mouse held + { + return MouseButton::LeftMouse; + } + else if (mouseState.buttons & 2) //Right mouse held + { + return MouseButton::RightMouse; + } + else if (mouseState.buttons & 4) //Middle mouse held + { + return MouseButton::MiddleMouse; + } + } +} + +KeyCode InputSystem::getKeyboardInput(InputMode inputMode) +{ + //If there is an event + al_get_next_event(mpKeyboardEventQueue, &mKeyboardEvent); + + if (mKeyboardEvent.type == inputMode) + { + return (KeyCode)mKeyboardEvent.keyboard.keycode; + } + +} \ No newline at end of file diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 3b0ac9de..e40ab2ff 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -1,5 +1,342 @@ +//#include +#include + +#include "Networker.h" + +#include "InputSystem.h" +#include "GraphicsLibrary.h" +#include "GameObject.h" +#include "Rock.h" +#include "Wall.h" +#include "PlayerController.h" + +//-------------------------Graphics Data------------------------- +GraphicsLibrary* pGraphics; +float screenSizeX = 1200.0; +float screenSizeY = 725.0; + +//-------------------------Input Data------------------------- +InputSystem* pInput; +bool bCanSpawnGameObject = false; + +//-------------------------Assets------------------------- +const std::string ASSET_PATH = "Assets\\"; +const std::string BACKGROUND_IMAGE_FILE = "Background_Image.jpg"; +const std::string ROCK_IMAGE_FILE = "Rock_Image.png"; +const std::string WALL_IMAGE_FILE = "Square_Image.png"; +const std::string PLAYER_IMAGE_FILE = "Player_Image.png"; +const std::string ARIAL_FONT_FILE = "ARIBL0.ttf"; +const int FONT_SIZE = 32; + +//-------------------------Colours------------------------- +Colour white(255, 255, 255, 255); +Colour wallColour; + +//-------------------------Asset Identifiers------------------------- +const std::string BACKGROUND_IMAGE_SPRITE_IDENTIFIER = "background_image"; +const std::string ROCK_SPRITE_IDENTIFIER = "rock_image"; +const std::string WALL_SPRITE_IDENTIFIER = "wall_image"; +const std::string PLAYER_SPRITE_IDENTIFIER = "player_image"; + +//-------------------------Game Data------------------------- +bool bGameIsRunning = true; +std::map gameObjectMap; + +int FPS = 60; +ALLEGRO_TIMER* timer = nullptr; +ALLEGRO_EVENT_QUEUE* eventQueue = nullptr; + +float wallSizeX = 150; +float wallSizeY = 15; +float wallBorderThickness = 5.0; + +//-------------------------GameObject Data------------------------- +GameObjectType currentGameObjectType; +std::string currentGameObjectTypeString; + +//-------------------------Player Data------------------------- +//PlayerController* pPlayerController; +shared_ptr pPlayerController; + +std::pair startingPlayerPos; +const std::pair STARTING_PLAYER_POSITION_SERVER = std::make_pair(300.0, 300.0); +const std::pair STARTING_PLAYER_POSITION_CLIENT = std::make_pair(900.0, 300.0); +float playerMoveSpeed = 0.5; + +//-------------------------Network Data------------------------- +Networker* Networker::mInstance = 0; +Networker* pNetworkManager; +PacketType packetTypeReceived = PacketType::PACKET_INVALID; +int networkID = 0; +std::string servPort = "1234"; +std::string clientPort = "2345"; + +bool init() +{ + bool bSuccessfulInit = false; + + //Setup the graphical window + pGraphics = new GraphicsLibrary(screenSizeX, screenSizeY); + bSuccessfulInit = pGraphics->init(); + + //Setup text + if (bSuccessfulInit) + bSuccessfulInit = pGraphics->initText(ASSET_PATH + ARIAL_FONT_FILE, FONT_SIZE, white); + + //Add images to the graphics library + pGraphics->loadImage(ASSET_PATH + BACKGROUND_IMAGE_FILE, BACKGROUND_IMAGE_SPRITE_IDENTIFIER); + pGraphics->loadImage(ASSET_PATH + ROCK_IMAGE_FILE, ROCK_SPRITE_IDENTIFIER); + pGraphics->loadImage(ASSET_PATH + WALL_IMAGE_FILE, WALL_SPRITE_IDENTIFIER); + pGraphics->loadImage(ASSET_PATH + PLAYER_IMAGE_FILE, PLAYER_SPRITE_IDENTIFIER); + + //Setup the input system + pInput = new InputSystem(); + if (bSuccessfulInit) + bSuccessfulInit = pInput->init(pGraphics); + + //Setup timer + timer = al_create_timer(1.0 / FPS); + eventQueue = al_create_event_queue(); + al_register_event_source(eventQueue, al_get_timer_event_source(timer)); + + //Init and return if it succeeded or not + return bSuccessfulInit; +} + +void start() +{ + //Default GameObject to spawn + currentGameObjectType = GameObjectType::ROCK; + currentGameObjectTypeString = "Rock"; + + wallColour = white; + + //If server + if (networkID == 0) + { + //Spawn player + //pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + pPlayerController = std::make_shared(PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER)); + pNetworkManager->addGameObject(pPlayerController, networkID); + networkID++; + + //Send it out + pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + + //Get client player + while (packetTypeReceived != PACKET_CREATE) + { + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + } + } + //If client + else if (networkID == 1) + { + //Get server player + while (packetTypeReceived != PACKET_CREATE) + { + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + } + + //Spawn player + //pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + pPlayerController = std::make_shared(PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER)); + pNetworkManager->addGameObject(pPlayerController, networkID); + networkID++; + + //Send it out + pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + } + + al_start_timer(timer); +} + +void update() +{ + //Get mouse down input + { + MouseButton mouseDownInput = pInput->getMouseInput(InputMode::MouseDown); + + //Handle input + switch (mouseDownInput) + { + case MouseButton::LeftMouse: + { + if (!bCanSpawnGameObject) + { + //Spawn current GameObject type + //GameObject* gameObjectToSpawn; + + switch (currentGameObjectType) + { + case GameObjectType::ROCK: + { + pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); + //gameObjectToSpawn = dynamic_cast(new Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + //gameObjectToSpawn = dynamic_cast>(Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + shared_ptr newRock = std::make_shared(Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + pNetworkManager->addGameObject(newRock, networkID); + //pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + pNetworkManager->sendGameObjectStateUDP(networkID, PacketType::PACKET_CREATE); + networkID++; + + break; + } + case GameObjectType::WALL: + { + pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); + //gameObjectToSpawn = dynamic_cast(new Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); + shared_ptr newWall = std::make_shared(Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); + pNetworkManager->addGameObject(newWall, networkID); + //pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + pNetworkManager->sendGameObjectStateUDP(networkID, PacketType::PACKET_CREATE); + networkID++; + + break; + } + + default: + { + std::cout << "INVALID GAMEOBJECT TYPE! CANNOT CREATE!\n"; + + break; + } + } + + //delete gameObjectToSpawn; + //gameObjectToSpawn = nullptr; + bCanSpawnGameObject = true; + } + } + + default: + break; + } + } + + //Get mouse up input + { + MouseButton mouseUpInput = pInput->getMouseInput(InputMode::MouseUp); + { + //Prevent spawning multiple gameobjects with mouse down - allow player to spawn after mouse is released again + if (mouseUpInput == MouseButton::LeftMouse) + bCanSpawnGameObject = false; + } + } + + //Get keyboard input + { + KeyCode keyCode = pInput->getKeyboardInput(InputMode::KeyPressed); + + switch (keyCode) + { + case KeyCode::R: + { + break; + } + + case KeyCode::Tab: + { + //Cycle throught GameObject types + currentGameObjectType = static_cast((currentGameObjectType + 1) % GameObjectType::ENUM_SIZE); + + //DO NOT SPAWN A PLAYER + if (currentGameObjectType == GameObjectType::PLAYER) + currentGameObjectType = static_cast((currentGameObjectType + 1) % GameObjectType::ENUM_SIZE); + + switch (currentGameObjectType) + { + case GameObjectType::INVALID: + currentGameObjectTypeString = "INVALID"; + break; + + case GameObjectType::ROCK: + currentGameObjectTypeString = "Rock"; + break; + + case GameObjectType::WALL: + currentGameObjectTypeString = "Wall"; + break; + + default: + currentGameObjectTypeString = "INVALID"; + break; + } + + break; + } + + case KeyCode::Esc: + { + //Quit game + bGameIsRunning = false; + return; + break; + } + case KeyCode::BACK: + { + //Players have IDs 0 and 1, DO NOT TOUCH THEM + if (networkID > 1) + { + //pNetworkManager->sendGameObjectState(networkID - 1, PacketType::PACKET_DELETE); + pNetworkManager->sendGameObjectStateUDP(networkID - 1, PacketType::PACKET_DELETE); + networkID--; + } + + break; + } + + default: + break; + } + } + + pNetworkManager->updateGameObjects(); +} + +void draw() +{ + //Background image + pGraphics->drawImage(BACKGROUND_IMAGE_SPRITE_IDENTIFIER, 0, 0); + + //Draw GameObjects + pNetworkManager->renderGameObjects(); + + //Text indicator of current GameObject Type + pGraphics->drawText(100, 50, "Current Object to Spawn: " + currentGameObjectTypeString, TextAlignment::ALIGN_LEFT); + + //Text indicators for instructions + { + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 50, "WASD - Move Player.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 100, "TAB - Change GameObject to Spawn.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 150, "Left Mouse - Spawn GameObject.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 200, "Backspace - Delete last GameObject.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 250, "ESC - Quit.", TextAlignment::ALIGN_LEFT); + } + + //Render it all + pGraphics->render(); +} + +void cleanup() +{ + //cleanup timer + al_destroy_timer(timer); + al_destroy_event_queue(eventQueue); + + //Cleanup network manager - cleans up GameObjects + pNetworkManager->cleanup(); + pNetworkManager = nullptr; + + //Cleanup input system + delete pInput; + pInput = nullptr; + + //Cleanup the graphics system + delete pGraphics; + pGraphics = nullptr; +} -#include "RoboCatPCH.h" #if _WIN32 @@ -17,9 +354,122 @@ int main(int argc, const char** argv) __argv = argv; #endif - SocketUtil::StaticInit(); + if (init()) + { + //Setup network manager + pNetworkManager = pNetworkManager->GetInstance(); + pNetworkManager->init(pGraphics, ROCK_SPRITE_IDENTIFIER, PLAYER_SPRITE_IDENTIFIER, playerMoveSpeed, wallColour/*, 60*/); + + //Prompt for isServer or not + std::string input; + std::cout << "Are you the server? Type 'y' for yes, anything else for no.\n"; + std::cin >> input; + bool bIsServer = false; + bool bSocketInitted = false; - SocketUtil::CleanUp(); + if (input == "y" || input == "Y") + { + bIsServer = true; + } + + //Setup server and client + + if (bIsServer) + { + //-------------------------Server code------------------------- + + //Prompt for client IP address + std::string clientIP; + std::cout << "Enter client IP address: \n"; + std::cin >> clientIP; + + bSocketInitted = pNetworkManager->initServerUDP(clientIP, servPort, clientPort); + if (bSocketInitted) + std::cout << "main.cpp --> server initted.\n"; + + //Server PlayerController is networkID 0 + networkID = 0; + startingPlayerPos = STARTING_PLAYER_POSITION_SERVER; + } + else + { + //-------------------------Client code------------------------- + + //Prompt for server IP address + std::string serverIP; + std::cout << "Enter server IP address: \n"; + std::cin >> serverIP; + + //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); + bSocketInitted = pNetworkManager->connectUDP(serverIP, servPort, clientPort); + if (bSocketInitted) + std::cout << "main.cpp --> client connected.\n"; + + //Client PlayerController is networkID 1 + networkID = 1; + startingPlayerPos = STARTING_PLAYER_POSITION_CLIENT; + } + + //If the socket in initted + if (bSocketInitted) + { + packetTypeReceived = PacketType::PACKET_INVALID; + + //Check for peer connected + while (packetTypeReceived != PacketType::PACKET_HELLO) + { + //Send hello packets + pNetworkManager->sendGameObjectStateUDP(-5, PacketType::PACKET_HELLO); + + //Keep receiving packsts + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + } + + //Setup + start(); + + //Since 0 and 1 are used for both player controllers, start everything else at 2 + networkID = 2; + + //Loop the game + while (bGameIsRunning) + { + ALLEGRO_EVENT ev; + al_get_next_event(eventQueue, &ev); + + if (ev.type == ALLEGRO_EVENT_TIMER) + { + //Update call + update(); + + //Network updates - send player data + pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); + + //Network update - receive packets + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + + //Network check for and deal with timed out packets + pNetworkManager->checkTimedOutPackets(); + + //If you receive a new GameObject, increment the network ID to keep spawning in sync! + if (packetTypeReceived == PacketType::PACKET_CREATE) + networkID++; + + //If you receive a delete packet, decrement the network ID to keep spawning in sync! + if (packetTypeReceived == PacketType::PACKET_DELETE) + networkID--; + + //Draw call + draw(); + } + } + + //Cleanup when done + cleanup(); + } + } + + std::cin.get(); return 0; -} +} \ No newline at end of file diff --git a/RoboCat/Src/MemoryBitStream.cpp b/RoboCat/Src/MemoryBitStream.cpp index 39958ad7..649ccee2 100644 --- a/RoboCat/Src/MemoryBitStream.cpp +++ b/RoboCat/Src/MemoryBitStream.cpp @@ -81,6 +81,7 @@ void OutputMemoryBitStream::ReallocBuffer( uint32_t inNewBitLength ) { //just need to memset on first allocation mBuffer = static_cast( std::malloc( inNewBitLength >> 3 ) ); + memset( mBuffer, 0, inNewBitLength >> 3 ); } else diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp new file mode 100644 index 00000000..70058381 --- /dev/null +++ b/RoboCat/Src/Networker.cpp @@ -0,0 +1,491 @@ +#include "Networker.h" +#include + +//Constructor +Networker::Networker() +{ + mbIsInitted = false; +} + +Networker::~Networker() +{ + cleanup(); +} + +void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour/*, float arrivalTime*/) +{ + if (mbIsInitted) + { + cleanup(); + } + + std::srand(time(NULL)); + + mpUDPSocket = new UDPSocketPtr(); + mpSocketAddressPtr = new SocketAddressPtr(); + mGameObjectsVec = std::vector>>(); + pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); + mOutputBitStreamQueue = std::priority_queue, std::vector>, myComp>(); + + //Data for GameObject replication + mpGraphicsLibrary = graphicsLibrary; + mRockSpriteIdentifier = rockSpriteIdentifier; + mPlayerSpriteIdentifier = playerSpriteIdentifier; + mPlayerMoveSpeed = playerMoveSpeed; + mWallColour = wallColour; + + mbIsInitted = true; +} + +void Networker::cleanup() +{ + //Cleanup map + std::vector >>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + delete it->second.get(); + it->second = nullptr; + } + mGameObjectsVec.clear(); + + //Cleanup delivery notification manager + delete pDeliveryNotificationManager; + pDeliveryNotificationManager = nullptr; + + (*mpUDPSocket)->CleanupSocket(); + delete mpUDPSocket; + mpUDPSocket = nullptr; + + delete mpSocketAddressPtr; + mpSocketAddressPtr = nullptr; + + SocketUtil::CleanUp(); + + mbIsInitted = false; +} + +bool Networker::initServerUDP(std::string clientIpAddress, std::string servPort, std::string clientPort) +{ + SocketUtil::StaticInit(); + + //Create Socket + UDPSocketPtr sock = SocketUtil::CreateUDPSocket(SocketAddressFamily::INET); + sock->SetNonBlockingMode(true); + + if (sock == nullptr) + { + SocketUtil::ReportError("Creating Client Socket"); + ExitProcess(1); + return false; + } + + SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString((clientIpAddress + ":" + clientPort).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + servPort).c_str()); + if (sockAddress == nullptr) + { + SocketUtil::ReportError("Creating Server Address"); + ExitProcess(1); + } + + if (sock->Bind(*sockAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding Server Socket"); + ExitProcess(1); + } + LOG("%s", "Server Socket Succesfully Binded!"); + + *mpSocketAddressPtr = clientAddress; + *mpUDPSocket = sock; + + if (*mpUDPSocket != nullptr) + return true; + return false; +} + +bool Networker::connectUDP(std::string serverIpAddress, std::string servPort, std::string clientPort) +{ + SocketUtil::StaticInit(); + + //Create Socket + UDPSocketPtr sock = SocketUtil::CreateUDPSocket(SocketAddressFamily::INET); + sock->SetNonBlockingMode(true); + + if (sock == nullptr) + { + SocketUtil::ReportError("Creating Client Socket"); + ExitProcess(1); + return false; + } + + SocketAddressPtr servAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + servPort).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + clientPort).c_str()); + if (sockAddress == nullptr) + { + SocketUtil::ReportError("Creating Server Address"); + ExitProcess(1); + } + + if (sock->Bind(*sockAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding Client Socket"); + ExitProcess(1); + } + LOG("%s", "Server Socket Succesfully Binded!"); + + *mpUDPSocket = sock; + *mpSocketAddressPtr = servAddress; + + if (*mpUDPSocket != nullptr) + return true; + return false; +} + +PacketType Networker::receiveGameObjectStateUDP() +{ + char buffer[1024]; + int32_t byteRecieve = (*mpUDPSocket)->ReceiveFrom(buffer, 1024, (**mpSocketAddressPtr)); + + if (byteRecieve > 0) + { + InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); + + //Read sequence number + if (pDeliveryNotificationManager->ReadAndProcessState(IMBStream)) + { + //Start reading + PacketType packetHeader; + IMBStream.Read(packetHeader); + int networkID; + IMBStream.Read(networkID); + + //If ID received is < 0, we are doing connection stuff + if (networkID < 0) + { + return packetHeader; + } + + GameObjectType receiveType; + IMBStream.Read(receiveType); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_HELLO: + break; + + case PacketType::PACKET_CREATE: + { + float posX; + float posY; + + IMBStream.Read(posX); + IMBStream.Read(posY); + + switch (receiveType) + { + case GameObjectType::ROCK: + { + //Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); + shared_ptr newRock = std::make_shared(Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier)); + mGameObjectsVec.push_back(pair>(networkID, newRock)); + + //delete newRock; + //newRock = nullptr; + + break; + } + + case GameObjectType::PLAYER: + { + //PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); + shared_ptr newPlayerController = std::make_shared(PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier)); + mGameObjectsVec.push_back(pair>(networkID, newPlayerController)); + + //delete newPlayerController; + //newPlayerController = nullptr; + + break; + + } + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thickness; + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thickness); + + //Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); + shared_ptr newWall = std::make_shared(Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness)); + mGameObjectsVec.push_back(pair>(networkID, newWall)); + + //delete newWall; + //newWall = nullptr; + + break; + } + } + + break; + } + + case PacketType::PACKET_UPDATE: + + if (mGameObjectsVec[networkID].second != nullptr) + { + float x; + float y; + + switch (receiveType) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + + IMBStream.Read(x); + IMBStream.Read(y); + + //Checking to absolutly make sure values are getting recieved + std::cout << "Recieved Position X: " << x << std::endl; + std::cout << "Recieved Position Y: " << y << std::endl; + + mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); + break; + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thiccness; + + //Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + Wall* wall = (Wall*)mGameObjectsVec[networkID].second.get(); + IMBStream.Read(x); + IMBStream.Read(y); + + wall->setPos(std::make_pair(x, y)); + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thiccness); + + wall->setWallSizeX(sizeX); + wall->setWallSizeY(sizeY); + wall->setWallThickness(thiccness); + + delete wall; + wall = nullptr; + + break; + } + } + } + else + { + //Report error + std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete element in map + if (mGameObjectsVec.size() > 0) + { + //std::vector>::iterator it; + std::vector>>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + + break; + } + + default: + return PacketType::PACKET_INVALID; + } + + return packetHeader; + } + } + else if (byteRecieve == -10053 /*|| byteRecieve == -10054*/) + { + LOG("%s", "Disconnected From Server"); + exit(0); + } + return PacketType::PACKET_INVALID; +} + +void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) +{ + OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream(); + + //Write state sent (packet sequence number and acks) + InFlightPacket* pInFlightPacket = pDeliveryNotificationManager->WriteState(*OMBStream); + + //Write packet header + OMBStream->Write(packetHeader); + + //If ID received is < 0, we are doing connection stuff + if (ID < 0) + { + //Write ID + OMBStream->Write(ID); + + //Send packet and return from the function + (*mpUDPSocket)->SendTo(OMBStream->GetBufferPtr(), OMBStream->GetBitLength(), (**mpSocketAddressPtr)); + + delete OMBStream; + OMBStream = nullptr; + return; + } + + OMBStream->Write(mGameObjectsVec[ID].second->getNetworkID()); + OMBStream->Write(mGameObjectsVec[ID].second->getGameObjectType()); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_CREATE: + case PacketType::PACKET_UPDATE: + + switch (mGameObjectsVec[ID].second->getGameObjectType()) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + + //For testing to absolutly make sure values are getting written + std::cout << "Position X: " << mGameObjectsVec[ID].second->getPosition().first << std::endl; + std::cout << "Position Y: " << mGameObjectsVec[ID].second->getPosition().second << std::endl; + + OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); + OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); + //pInFlightPacket->SetTransmissionData(0, mGameObjectsVec[ID].second); + pInFlightPacket->SetTransmissionData(0, mGameObjectsVec[ID].second.get()); + //pInFlightPacket = nullptr; + break; + + case GameObjectType::WALL: + + //Wall* wall = (Wall*)mGameObjectsVec[ID].second; + Wall* wall = (Wall*)mGameObjectsVec[ID].second.get(); + OMBStream->Write(wall->getPosition().first); + OMBStream->Write(wall->getPosition().second); + OMBStream->Write(wall->getWallSizeX()); + OMBStream->Write(wall->getWallSizeY()); + OMBStream->Write(wall->getWallThickness()); + pInFlightPacket->SetTransmissionData(0, wall); + //pInFlightPacket = nullptr; + + delete wall; + wall = nullptr; + + break; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete it on the sender's end + if (mGameObjectsVec.size() > 0) + { + //std::vector>::iterator it; + std::vector>>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + pInFlightPacket->SetTransmissionData(0, it->second.get()); + //pInFlightPacket = nullptr; + + break; + } + } + } + } + + break; + + default: + return; + } + + //Make sure to send the first two gameobjects so both players can connect and be displayed on screen + if (ID > 1) + { + //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting + float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); + mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); + OMBStream = nullptr; + + //If the queue has more than 10 elements is it + if (mOutputBitStreamQueue.size() >= 10) + { + //Iterate though the queue + //for (size_t i = mOutputBitStreamQueue.size() - 1; i >= 0 ; i--) + for (size_t i = 0; i < mOutputBitStreamQueue.size(); i++) + { + //Drop some packets + if (i % 3 != 0) + { + //Send packet + (*mpUDPSocket)->SendTo(mOutputBitStreamQueue.top().second->GetBufferPtr(), mOutputBitStreamQueue.top().second->GetBitLength(), (**mpSocketAddressPtr)); + delete mOutputBitStreamQueue.top().second; + mOutputBitStreamQueue.pop(); + } + } + } + } + else + { + //Send packet + (*mpUDPSocket)->SendTo(OMBStream->GetBufferPtr(), OMBStream->GetBitLength(), (**mpSocketAddressPtr)); + + delete OMBStream; + OMBStream = nullptr; + } +} + +void Networker::checkTimedOutPackets() +{ + pDeliveryNotificationManager->ProcessTimedOutPackets(); +} + +void Networker::addGameObject(/*GameObject**/ shared_ptr objectToAdd, int networkID) +{ + mGameObjectsVec.push_back(pair>(networkID, objectToAdd)); +} + +void Networker::updateGameObjects() +{ + std::vector>>::iterator it; + //std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + it->second->update(); + } +} + +void Networker::renderGameObjects() +{ + std::vector>>::iterator it; + //std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + it->second->draw(); + } +} \ No newline at end of file diff --git a/RoboCat/Src/PlayerController.cpp b/RoboCat/Src/PlayerController.cpp new file mode 100644 index 00000000..52020c7f --- /dev/null +++ b/RoboCat/Src/PlayerController.cpp @@ -0,0 +1,148 @@ +#include "PlayerController.h" +#include "RoboCatPCH.h" + +PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary) + : GameObject(GameObjectType::PLAYER, networkID, graphicsLibrary) +{ + //Key down + pInputKeyDown = new InputSystem(); + pInputKeyDown->init(graphicsLibrary); + + //Key up + pInputKeyUp = new InputSystem(); + pInputKeyUp->init(graphicsLibrary); + + mMoveSpeed = 0.0; +} + +PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float moveSpeed, const std::string spriteIdentifier) + : GameObject(GameObjectType::PLAYER, networkID, graphicsLibrary, position, spriteIdentifier) +{ + //Key down + pInputKeyDown = new InputSystem(); + pInputKeyDown->init(graphicsLibrary); + + //Key up + pInputKeyUp = new InputSystem(); + pInputKeyUp->init(graphicsLibrary); + + mMoveSpeed = moveSpeed; +} + +void PlayerController::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const +{ + //Do nothing, the data will be stale if re-sent, since it is sending update packets every frame (only latest data matters) +} + +void PlayerController::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const +{ + //Cool, already taken care of +} + +PlayerController::~PlayerController() +{ + //Key down + delete pInputKeyDown; + pInputKeyDown = nullptr; + + //Key up + delete pInputKeyUp; + pInputKeyUp = nullptr; +} + +void PlayerController::update() +{ + //Get keyboard input - KeyPressed + { + KeyCode keyCode = pInputKeyDown->getKeyboardInput(InputMode::KeyPressed); + + switch (keyCode) + { + case KeyCode::W: + { + //Move up + bShouldMoveUp = true; + break; + } + + case KeyCode::A: + { + //Move left + bShouldMoveLeft = true; + break; + } + + case KeyCode::S: + { + //Move down + bShouldMoveDown = true; + break; + } + + case KeyCode::D: + { + //Move right + bShouldMoveRight = true; + break; + } + + default: + break; + } + } + + //Get keyboard input - KeyReleased + { + KeyCode keyCode = pInputKeyUp->getKeyboardInput(InputMode::KeyReleased); + + switch (keyCode) + { + case KeyCode::W: + { + //Stop moving up + bShouldMoveUp = false; + break; + } + + case KeyCode::A: + { + //Stop moving left + bShouldMoveLeft = false; + break; + } + + case KeyCode::S: + { + //Stop moving down + bShouldMoveDown = false; + break; + } + + case KeyCode::D: + { + //Stop moving right + bShouldMoveRight = false; + break; + } + + default: + break; + } + } + + //Apply move based on holding keys + if (bShouldMoveUp) + mPosition.second -= mMoveSpeed; + if (bShouldMoveDown) + mPosition.second += mMoveSpeed; + if (bShouldMoveLeft) + mPosition.first -= mMoveSpeed; + if (bShouldMoveRight) + mPosition.first += mMoveSpeed; +} + +void PlayerController::draw() +{ + //Draw sprite at mPosition + pGraphicsLibrary->drawScaledImage(mSPRITE_IDENTIFIER, mPosition.first, mPosition.second, 0.25, 0.25); +} \ No newline at end of file diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp new file mode 100644 index 00000000..ac5e2595 --- /dev/null +++ b/RoboCat/Src/Rock.cpp @@ -0,0 +1,41 @@ +#include "Rock.h" +#include "RoboCatPCH.h" + +Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary) + : GameObject(GameObjectType::ROCK, networkID, graphicsLibrary) +{ + +} + +Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier) + : GameObject(GameObjectType::ROCK, networkID, graphicsLibrary, position, spriteIdentifier) +{ + +} + +void Rock::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const +{ + //Re-send packet + inDeliveryNotificationManager->ResendPacket(mNetworkID, packetSequenceNum); +} + +void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const +{ + //Cool, already taken care of +} + +Rock::~Rock() +{ + +} + +void Rock::update() +{ + +} + +void Rock::draw() +{ + //Draw sprite at mPosition + pGraphicsLibrary->drawScaledImage(mSPRITE_IDENTIFIER, mPosition.first, mPosition.second, 0.25, 0.25); +} \ No newline at end of file diff --git a/RoboCat/Src/TCPSocket.cpp b/RoboCat/Src/TCPSocket.cpp index 494f776c..e4e9a20c 100644 --- a/RoboCat/Src/TCPSocket.cpp +++ b/RoboCat/Src/TCPSocket.cpp @@ -95,11 +95,16 @@ int TCPSocket::SetNonBlockingMode(bool inShouldBeNonBlocking) } } -TCPSocket::~TCPSocket() +void TCPSocket::CleanupSocket() { #if _WIN32 - closesocket( mSocket ); + closesocket(mSocket); #else - close( mSocket ); + close(mSocket); #endif } + +TCPSocket::~TCPSocket() +{ + CleanupSocket(); +} \ No newline at end of file diff --git a/RoboCat/Src/Timing.cpp b/RoboCat/Src/Timing.cpp index 9a8be86c..114a8365 100644 --- a/RoboCat/Src/Timing.cpp +++ b/RoboCat/Src/Timing.cpp @@ -1,5 +1,5 @@ #include "RoboCatPCH.h" - +#include "Timing.h" #if !_WIN32 #include diff --git a/RoboCat/Src/UDPSocket.cpp b/RoboCat/Src/UDPSocket.cpp index adf859f7..9509aa3d 100644 --- a/RoboCat/Src/UDPSocket.cpp +++ b/RoboCat/Src/UDPSocket.cpp @@ -68,11 +68,7 @@ int UDPSocket::ReceiveFrom( void* inToReceive, int inMaxLength, SocketAddress& o UDPSocket::~UDPSocket() { -#if _WIN32 - closesocket( mSocket ); -#else - close( mSocket ); -#endif + CleanupSocket(); } @@ -98,3 +94,11 @@ int UDPSocket::SetNonBlockingMode( bool inShouldBeNonBlocking ) } } +void UDPSocket::CleanupSocket() +{ +#if _WIN32 + closesocket(mSocket); +#else + close(mSocket); +#endif +} \ No newline at end of file diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp new file mode 100644 index 00000000..9b36e3ee --- /dev/null +++ b/RoboCat/Src/Wall.cpp @@ -0,0 +1,49 @@ +#include "Wall.h" +#include "RoboCatPCH.h" + +Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary) + : GameObject(GameObjectType::WALL, networkID, graphicsLibrary) +{ + mSizeX = 1.0; + mSizeY = 2.0; + + mColour = Colour(255, 255, 255, 255); + mThickness = 1.0; +} + +Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float sizeX, float sizeY, Colour colour, float thickness) + : GameObject(GameObjectType::WALL, networkID, graphicsLibrary, position) +{ + mSizeX = sizeX; + mSizeY = sizeY; + + mColour = colour; + mThickness = thickness; +} + +void Wall::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const +{ + //Re-send packet + inDeliveryNotificationManager->ResendPacket(mNetworkID, packetSequenceNum); +} + +void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const +{ + //Cool, already taken care of +} + +Wall::~Wall() +{ + +} + +void Wall::update() +{ + +} + +void Wall::draw() +{ + //Draw scaled sprite at mPosition + pGraphicsLibrary->drawRectangle(mPosition.first, mPosition.second, mPosition.first + mSizeX, mPosition.second + mSizeY, mColour, mThickness); +} \ No newline at end of file diff --git a/TO-DO.txt b/TO-DO.txt new file mode 100644 index 00000000..8c715421 --- /dev/null +++ b/TO-DO.txt @@ -0,0 +1,43 @@ +Assignment Requirements: + +"Enhance" your previous game to simulate jitter, latency, and dropped packets, and add a reliability layer to deal with these potential pitfalls. Your game must: + +. Pass all checks on github +. Be able to open a window +. Have at least two players able to connect at once +--> These are the minimum requirements for the assignment to be gradable. + +- Packets are occasionally "dropped," by random chance or by some algorithm. Dropped packets are registered as having been sent by the sender, but do not actually call the socket's send() function. Random delay is introduced to some packet's send time, and packets can be delivered out of order. +- Game state is sent through a reliability layer. Important data is sent through guaranteed mechanisms, while data that does not require guaranteed delivery is sent through simpler mechanisms. Important data is resilient to jitter, out-of-order delivery, and high latency. + +--------------------------------------------------------------- +Notes and Ideas: + +--> Chapter 7 slide 14 gives a good explanation of how this can be done + +CLASS NOTES 19/04/2022: +For handling delviery failure and success, InFlightPacket implicit constructor call, need to give it transmission data. How? +--> override void HandleDelivery... in inherited classes +--> Pass an instance of the class to InFlightPacket +--> Tip: use std::shared_ptr as type so you don't have to worry about memory leaks +--> InFlightPacket::SetTransmissionData(any key (0 is best as it is the index and we only want 1 thing in container), instance of class); + +--> TransmissionDataPtr classInstance = std::make_shared(constructor args); +--> InFlightPacket* packet = deliveryNotificationManager->WriteState(packetData); +--> packet->SetTransmissionData(index, classInstance); +--> //Write rest of packetData + +----------------------------------------------------------------- + +Simulating Latency/Jitter + +1. Along with header, send a float for arrival time + Note: Arrival time should be +/- a random amount, for simulating jitter + +2. Insert inflightpacket into a queue (couple ways to do this) + Note: one way the professor recommended is to enqueue the bytes recieve and split it that way (memory management yaaaaay) + +3. Sort Queue based on arrival time (this will simulate jitter) + +4. Each frame of the game, we process only the packets that arrvive based on the current arrival time + Note: if arrival time = x seconds, we only process the packets x seconds ago \ No newline at end of file