RyanHub – file viewer
filename: src/main.cpp
branch: main
back to repo
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <math.h>

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <filesystem>

#include "networking.h"
#include "renderer.h"
#include "raycaster.h"
#include "UI.h"

#include "constants.h"

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::thread;

// NETWORK
Network network;
char inBuff[IN_OUT_BUFF_SIZE]; // data to receive
char outBuff[IN_OUT_BUFF_SIZE]; // data to send
size_t bytesReceived;

// PLAYERS
GLfloat positions[STARTING_PARAMETER_COUNT] = {}; // our x,y
GLfloat otherPlayerPositions[STARTING_PARAMETER_COUNT] = {}; // other player x, y
GLfloat vertices1[VERTICES_SIZE]; // our controlled character matrix
GLfloat vertices2[VERTICES_SIZE]; // other player matrix

// RENDERING
GLFWwindow* window;

// GAMESTATE
int GAMESTATE = START;
bool connected = false;
double xpos = 0, ypos = 0; // mouse
bool firstMouse = true;
double lastX = 0, lastY = 0;
bool shooting = false;
int hitCount = 0;
float deltaTime = 1 / 60; // stores actual speed of the game, 1/60 is placeholder
double lastShotTime = 0.0;
bool newImageAvaliable = false;
string msg = "";

static void updatePlayer(GLfloat* newPositions, GLfloat* playerVerticies)
{
	// newPositions comes in as [x, y], vertices is [x, y, z, r, g, b, ... ... ]
	playerVerticies[0] = newPositions[0] + PLAYER_SIZE; // Top right x
	playerVerticies[1] = newPositions[1] + PLAYER_SIZE; // Top right y

	playerVerticies[6] = newPositions[0] - PLAYER_SIZE; // Top left x
	playerVerticies[7] = newPositions[1] + PLAYER_SIZE; // Top left y

	playerVerticies[12] = newPositions[0] - PLAYER_SIZE; // Bottom left x
	playerVerticies[13] = newPositions[1] - PLAYER_SIZE; // Bottom left y

	playerVerticies[18] = newPositions[0] + PLAYER_SIZE; // Bottom right x
	playerVerticies[19] = newPositions[1] - PLAYER_SIZE; // Bottom right y
}
static bool isCollision(GLfloat xPos, GLfloat yPos) {
	int mapXpos = static_cast<int>((xPos + 1.0f) / tileSize);
	int mapYpos = static_cast<int>((1.0f - yPos) / tileSize);

	if (mapXpos < 0) mapXpos = 0;
	if (mapYpos < 0) mapYpos = 0;
	if (mapXpos >= mapX) mapXpos = mapX - 1;
	if (mapYpos >= mapY) mapYpos = mapY - 1;

	return map[mapYpos * mapX + mapXpos] == 1;
}
bool isCrosshairOverPlayer(float xpos, float ypos, const GLfloat* myPos, const GLfloat* otherPos) {
	float dx = otherPos[0] - myPos[0];
	float dy = otherPos[1] - myPos[1];
	float distance = sqrt(dx * dx + dy * dy);

	float angleToOther = atan2f(dx, dy) * (180.0f / 3.14159f);
	float relativeAngle = angleToOther - xpos;
	if (relativeAngle < -180.0f) relativeAngle += 360.0f;
	if (relativeAngle > 180.0f) relativeAngle -= 360.0f;

	float screenX = (relativeAngle / (RAY_NUM * 0.5f));

	float verticalAngleToOther = 0.0f;
	float relativePitch = verticalAngleToOther - ypos;
	float screenY = (relativePitch / 100.0f);

	return fabs(screenX) < (0.005 / distance) && fabs(screenY) < (0.01 / distance); // adjust numbers for hitbox
}
static void processInput(SOCKET sock)
{
	float moveAmount = MOVE_SPEED * deltaTime; // scales move speed by framerate

	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
	double x, y;
	glfwGetCursorPos(window, &x, &y);

	if (firstMouse) {
		lastX = x;
		lastY = y;
		firstMouse = false;
	}

	double xoffset = x - lastX;
	double yoffset = y - lastY;
	lastX = x;
	lastY = y;

	xoffset /= MOUSE_SENS;
	yoffset /= MOUSE_SENS;
	xpos += xoffset;
	ypos += yoffset;
	if (ypos > 50) ypos = 50;
	if (ypos < -50) ypos = -50;
	if (xpos < 0) xpos += 360;
	if (xpos >= 360) xpos -= 360;


	static double shotCooldown = 1.0 / 60.0;
	double currentTime = glfwGetTime();
	if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
		if (currentTime - lastShotTime >= shotCooldown) {
			shooting = true;
			lastShotTime = currentTime;
		}
		else {
			shooting = false;
		}
	}
	else {
		shooting = false;
		GLfloat newX = positions[0];
		GLfloat newY = positions[1];

		if ((glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)) {
			newX -= moveAmount * -sin(degToRad(xpos));
			newY += moveAmount * cos(degToRad(xpos));
		}
		if ((glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)) {
			newX += moveAmount * -sin(degToRad(xpos));
			newY -= moveAmount * cos(degToRad(xpos));
		}
		if ((glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)) {
			newX -= moveAmount * cos(degToRad(xpos));
			newY += moveAmount * sin(degToRad(xpos));
		}
		if ((glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)) {
			newX += moveAmount * cos(degToRad(xpos));
			newY -= moveAmount * sin(degToRad(xpos));
		}

		if (!isCollision(newX, newY)) {
			positions[0] = newX;
			positions[1] = newY;
		}
		else {
			if (!isCollision(newX, positions[1])) {
				positions[0] = newX;
			}
			if (!isCollision(positions[0], newY)) {
				positions[1] = newY;
			}
		}
		updatePlayer(positions, vertices1);
	}
	if (GAMESTATE == DEBUG) {
		GLfloat newX = otherPlayerPositions[0];
		GLfloat newY = otherPlayerPositions[1];
		if ((glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)) {
			newY += moveAmount;
		}
		if ((glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)) {
			newY -= moveAmount;
		}
		if ((glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS)) {
			newX -= moveAmount;
		}
		if ((glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS)) {
			newX += moveAmount;
		}

		if (!isCollision(newX, newY)) {
			otherPlayerPositions[0] = newX;
			otherPlayerPositions[1] = newY;
		}
		else {
			if (!isCollision(newX, otherPlayerPositions[1])) {
				otherPlayerPositions[0] = newX;
			}
			if (!isCollision(otherPlayerPositions[0], newY)) {
				otherPlayerPositions[1] = newY;
			}
		}
		updatePlayer(otherPlayerPositions, vertices2);
	}
}

static bool sendStatePacket(SOCKET sock) {
	char outBuff[IN_OUT_BUFF_SIZE];
	int offset = 0;

	// Use header 1 for the full state packet
	outBuff[offset++] = 1;

	// Serialize positions[0] as a network order uint32_t
	uint32_t netPosX;
	memcpy(&netPosX, &positions[0], sizeof(float));
	netPosX = htonl(netPosX);
	memcpy(outBuff + offset, &netPosX, sizeof(uint32_t));
	offset += sizeof(uint32_t);

	// Serialize positions[1] as a network order uint32_t
	uint32_t netPosY;
	memcpy(&netPosY, &positions[1], sizeof(float));
	netPosY = htonl(netPosY);
	memcpy(outBuff + offset, &netPosY, sizeof(uint32_t));
	offset += sizeof(uint32_t);

	// Serialize shooting flag as uint8_t 1 for true 0 for false
	outBuff[offset++] = shooting ? 1 : 0;

	// Serialize hitCount as int32_t in network order
	int32_t netHitCount = htonl(hitCount);
	memcpy(outBuff + offset, &netHitCount, sizeof(int32_t));
	offset += sizeof(int32_t);

	// Serialize GAMESTATE as int32_t in network order
	int32_t gameState = htonl(GAMESTATE);
	//cout << "sending " << gameState << endl;

	memcpy(outBuff + offset, &gameState, sizeof(int32_t));
	offset += sizeof(int32_t);

	return network.sendData(sock, outBuff, offset);
}
static bool sendPlayerTexture(SOCKET sock) {
	std::ifstream textureFile("textures/playerTexture.png", std::ios::binary | std::ios::ate);
	if (!textureFile.is_open()) {
		std::cerr << "Failed to open texture file for reading." << endl;
		return false;
	}

	std::streamsize fileSize = textureFile.tellg();
	textureFile.seekg(0, std::ios::beg);

	if (fileSize > IN_OUT_BUFF_SIZE - sizeof(uint8_t) - sizeof(uint32_t)) {
		std::cerr << "Texture file is too large to send." << endl;
		textureFile.close();
		return false;
	}
	std::vector<char> buffer(static_cast<size_t>(fileSize));
	if (!textureFile.read(buffer.data(), fileSize)) {
		std::cerr << "Failed to read texture file." << endl;
		textureFile.close();
		return false;
	}
	textureFile.close();

	char outBuff[IN_OUT_BUFF_SIZE];
	int offset = 0;
	outBuff[offset++] = 2;

	uint32_t netFileSize = htonl(static_cast<uint32_t>(fileSize));
	memcpy(outBuff + offset, &netFileSize, sizeof(uint32_t));
	offset += sizeof(uint32_t);

	memcpy(outBuff + offset, buffer.data(), static_cast<size_t>(fileSize));
	offset += static_cast<int>(fileSize);

	newImageAvaliable = true;

	return network.sendData(sock, outBuff, offset);
}
static void handlePacket(const char* packet, size_t packetSize) {
	int offset = 0;
	uint8_t header = packet[offset++];

	if (header == 1) {
		// Full state packet expects 2 uint32_t positions, 1 byte for shooting, 1 int32_t hitCount
		if (packetSize >= offset + (2 * sizeof(uint32_t) + sizeof(uint8_t) + sizeof(int32_t))) {
			// Deserialize positions[0]
			uint32_t netPosX;
			memcpy(&netPosX, packet + offset, sizeof(uint32_t));
			netPosX = ntohl(netPosX);
			float posX;
			memcpy(&posX, &netPosX, sizeof(float));
			offset += sizeof(uint32_t);

			// Deserialize positions[1]
			uint32_t netPosY;
			memcpy(&netPosY, packet + offset, sizeof(uint32_t));
			netPosY = ntohl(netPosY);
			float posY;
			memcpy(&posY, &netPosY, sizeof(float));
			offset += sizeof(uint32_t);

			// Deserialize shooting flag
			uint8_t shootFlag = packet[offset++];
			bool remoteShooting = (shootFlag != 0);

			// Deserialize hitCount
			int32_t netHitCount;
			memcpy(&netHitCount, packet + offset, sizeof(int32_t));
			int receivedHitCount = ntohl(netHitCount);
			offset += sizeof(int32_t);			
			
			// Deserialize GAMESTATE
			int32_t state;
			memcpy(&state, packet + offset, sizeof(int32_t));
			int receivedstate = ntohl(state);
			offset += sizeof(int32_t);

			//cout << "receiving " << receivedstate << endl;

			// Update

			if (receivedstate == WIN) {
				GAMESTATE = LOSS; // we really only detect when we lose and send that but checking anyway
			}
			else if (receivedstate == LOSS) {
				GAMESTATE = WIN;
				msg = "YOU ARE A WINNER";
			}

			otherPlayerPositions[0] = posX;
			otherPlayerPositions[1] = posY;
			updatePlayer(otherPlayerPositions, vertices2);

			if (receivedHitCount >= maxHealth) {
				cout << "You died." << endl;
				msg = "YOU ARE A LOSER";
				GAMESTATE = LOSS;
			}
		}
	}
	else if (header == 2) {
		// Texture packet

		cout << "recieved texture packet" << endl;

		int offset = 1;
		if (packetSize >= offset + sizeof(uint32_t)) {
			// Deserialize texture data
			uint32_t netFileSize;
			memcpy(&netFileSize, packet + offset, sizeof(uint32_t));
			uint32_t fileSize = ntohl(netFileSize);
			offset += sizeof(uint32_t);

			// Check if the packet contains the full texture data
			if (packetSize >= offset + fileSize) {
				// Extract the texture data
				const char* textureData = packet + offset;

				// Save the texture data
				std::ofstream outFile("textures/receivedPlayerTexture.png", std::ios::binary);
				if (outFile.is_open()) {
					outFile.write(textureData, fileSize);
					outFile.close();

					// Reload texture
					//textureIDs[2] = loadTexture("textures/receivedPlayerTexture.png");
				}
				else {
					std::cerr << "Failed to open file for writing texture." << endl;
				}
			}
			else {
				std::cerr << "Incomplete texture data received." << endl;
			}
		}
		else {
			std::cerr << "Texture packet does not contain size information." << endl;
		}
	}
	else {
		// Unknown
	}
}


/* TODO:
 x wall collisions
 x other player rendering
 x textures
 x texture coordinates
 x fix hit detection
 ? texture mapped walls
 x separate rendering functions
 x separate raycasting functions
 x framerate based shooting
 x UI 
 x start menu
 x end menu
 x character editor
 x character texture sending
 x text and typing
*/




int main() {
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	window = glfwCreateWindow(SCREEN_X, SCREEN_Y, "AWESOME GAME", nullptr, nullptr);
	//window = glfwCreateWindow(SCREEN_X, SCREEN_Y, "AWESOME GAME", glfwGetPrimaryMonitor(), nullptr);

	if (window == nullptr) {
		cout << "Failed to create GLFW window" << endl;
		glfwTerminate();
		WSACleanup();
		return -1;
	}
	glfwMakeContextCurrent(window);
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		cout << "Failed to initialize GLAD" << endl;
		return -1;
	}
	glViewport(0, 0, SCREEN_X, SCREEN_Y);

	GLuint dummyTexture = loadTexture("textures/dummyTexture.png"); // 0
	GLuint wallTexture = loadTexture("textures/dummyTexture.png"); // wall texture is broken for now...
	GLuint playerTexture = loadTexture("textures/playerTexture.png"); // previously saved texture but will be updated after main menu
	GLuint buttonTextures = loadTexture("textures/buttons.png");
	GLuint textTextures = loadTexture("textures/boldText.png");
	// ensure to update frag shader when adding more textures

	GLuint textureIDs[] = { dummyTexture, wallTexture, playerTexture, buttonTextures, textTextures };
	int textureCount = sizeof(textureIDs) / sizeof(textureIDs[0]);

	Shader shaderProgram("shaders/default.vert", "shaders/default.frag");

	Renderer renderer(shaderProgram, textureIDs, textureCount);

	shaderProgram.Activate();
	glUniform1f(glGetUniformLocation(shaderProgram.ID, "scale"), 1.0f);

	glDisable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);

	SOCKET sock = INVALID_SOCKET;

	while (!glfwWindowShouldClose(window)) {

		bool textBoxEditMode = false;
		
		sock = INVALID_SOCKET;

		SOCKET listeningSocket = INVALID_SOCKET;
		SOCKET client_socket = INVALID_SOCKET;
		sockaddr_in client;
		int clientSize = sizeof(client);
		bool waitingForClient = false;

		string placeholderText = "WELCOME, SELECT A MODE TO BEGIN.";
		const float btnY = 0.15f;
		const float hostBtnX = -0.05f;
		const float joinBtnX = 0.35f;
		const float soloBtnX = 0.75f;
		const float btnW = 300.0f;
		const float btnH = 400.0f;
		const float btnWPressed = 290.0f;
		const float btnHPressed = 390.0f;
		Button hostButton = Button(btnW, btnH, hostBtnX, btnY, 200, 50, 50, 4, "HOST");
		Button joinButton = Button(btnW, btnH, joinBtnX, btnY, 50, 200, 50, 4, "JOIN");
		Button soloButton = Button(btnW, btnH, soloBtnX, btnY, 50, 50, 200, 4, "SOLO");
		Text message = Text(1000, 0.35, 0, 4, "ENTER HOST IP ADRESS:");
		TextBox IPTextBox = TextBox(600, 50, 0.60, -.250, 4, "__.__.__...");
		DrawingBoard drawingBoard(900.0f, 300.0f, -0.65f, 0.1f, "textures/playerTexture.png");
		Text drawDesc = Text(800, -0.65, 0.8, 4, "DRAW YOUR PLAYER!");

		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
		while (!glfwWindowShouldClose(window)) { // main menu

			if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
				glfwSetWindowShouldClose(window, true);
			}

			glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			// networking
			if (waitingForClient && listeningSocket != INVALID_SOCKET) {
				client_socket = accept(listeningSocket, (sockaddr*)&client, &clientSize);
				if (client_socket == INVALID_SOCKET) {
					int err = WSAGetLastError();
					if (err != WSAEWOULDBLOCK) {
						placeholderText = "Client connection failed!";
						closesocket(listeningSocket);
						listeningSocket = INVALID_SOCKET;
						WSACleanup();
						waitingForClient = false;
					}
				}
				else {
					closesocket(listeningSocket);
					listeningSocket = INVALID_SOCKET;
					sock = client_socket;
					waitingForClient = false;
					GAMESTATE = SERVER;
					placeholderText = "Client connected!";
					for (int i = 0; i < STARTING_PARAMETER_COUNT; i++) positions[i] = defaultServerPos[i];
					for (int i = 0; i < STARTING_PARAMETER_COUNT; i++) otherPlayerPositions[i] = defaultClientPos[i];
					updatePlayer(positions, vertices1);
					updatePlayer(otherPlayerPositions, vertices2);
					break;
				}
			}

			Text titleText(SCREEN_X, .35, 0.7f, 4, "AWESOME GAME");
			titleText.pushToRenderer(&renderer);

			Text statusText(SCREEN_X, 0.35, 0.5f, 4, placeholderText);
			statusText.pushToRenderer(&renderer);

			Text ipLabel(600, 0.1f, -.250, 4, "IP ADDRESS:");
			ipLabel.pushToRenderer(&renderer);
			IPTextBox.pushToRenderer(&renderer);

			// drawing board logic
			drawingBoard.isClicked(window);
			drawingBoard.pushToRenderer(&renderer);


			// button visual feedback
			Button hostButton(btnW, btnH, hostBtnX, btnY, 200, 50, 50, 4, "HOST");
			Button joinButton(btnW, btnH, joinBtnX, btnY, 50, 200, 50, 4, "JOIN");
			Button soloButton(btnW, btnH, soloBtnX, btnY, 50, 50, 200, 4, "SOLO");

			bool hostPressed = hostButton.isClicked(window);
			bool joinPressed = joinButton.isClicked(window);
			bool soloPressed = soloButton.isClicked(window);

			bool hostHovered = hostButton.isHovered(window);
			bool joinHovered = joinButton.isHovered(window);
			bool soloHovered = soloButton.isHovered(window);

			Button hostBtnDraw = hostButton;
			Button joinBtnDraw = joinButton;
			Button soloBtnDraw = soloButton;

			if (hostPressed) {
				hostBtnDraw = Button(btnWPressed, btnHPressed, hostBtnX, btnY, 255, 50, 50, 4, "HOST");
			}
			else if (hostHovered) {
				hostBtnDraw = Button(btnW, btnH, hostBtnX, btnY, 230, 50, 50, 4, "HOST");
			}
			if (joinPressed) {
				joinBtnDraw = Button(btnWPressed, btnHPressed, joinBtnX, btnY, 50, 255, 50, 4, "JOIN");
			}
			else if (joinHovered) {
				joinBtnDraw = Button(btnW, btnH, joinBtnX, btnY, 50, 230, 50, 4, "JOIN");
			}
			if (soloPressed) {
				soloBtnDraw = Button(btnWPressed, btnHPressed, soloBtnX, btnY, 50, 50, 255, 4, "SOLO");
			}
			else if (soloHovered) {
				soloBtnDraw = Button(btnW, btnH, soloBtnX, btnY, 50, 50, 230, 4, "SOLO");
			}

			hostBtnDraw.pushToRenderer(&renderer);
			joinBtnDraw.pushToRenderer(&renderer);
			soloBtnDraw.pushToRenderer(&renderer);

			// instructions
			Text hostDesc(1600, 0.35, -0.5f, 4, "* HOST: STARTS A SERVER FOR OTHERS TO JOIN.");
			Text joinDesc(1600, 0.35, -0.6f, 4, "* JOIN: CONNECTS TO EXISTING SERVER BY IP.");
			Text soloDesc(1600, 0.35, -0.7f, 4, "* SOLO: STARTS A SINGLE PLAYER SESSION.");
			hostDesc.pushToRenderer(&renderer);
			joinDesc.pushToRenderer(&renderer);
			soloDesc.pushToRenderer(&renderer);
			drawDesc.pushToRenderer(&renderer);

			// button logic
			if (hostPressed && !waitingForClient) {
				listeningSocket = network.serverSetup();
				if (listeningSocket == INVALID_SOCKET) {
					placeholderText = "SERVER SETUP ERROR!";
				}
				else {
					waitingForClient = true;
					placeholderText = "WAITING FOR CLIENT ON: " + network.printLocalIPAddress();
				}
			}
			if (joinPressed) {
				if (listeningSocket != INVALID_SOCKET) {
					closesocket(listeningSocket);
					listeningSocket = INVALID_SOCKET;
					waitingForClient = false;
				}
				if (IPTextBox.message.empty() || IPTextBox.message == "PLEASE ENTER IP HERE.") {
					IPTextBox.message = "PLEASE ENTER IP HERE.";
					placeholderText = "PLEASE ENTER A VALID IP ADDRESS.";
				}
				else {
					placeholderText = "IP ADDRESS ERROR TRY AGAIN!";
					IPTextBox.pushToRenderer(&renderer);

					sock = network.clientSetup(IPTextBox.message);
					if (sock == INVALID_SOCKET) {
						placeholderText = "IP ADDRESS ERROR TRY AGAIN!";
					}
					else {
						GAMESTATE = CLIENT;
						for (int i = 0; i < STARTING_PARAMETER_COUNT; i++) positions[i] = defaultClientPos[i];
						for (int i = 0; i < STARTING_PARAMETER_COUNT; i++) otherPlayerPositions[i] = defaultServerPos[i];
						updatePlayer(positions, vertices1);
						updatePlayer(otherPlayerPositions, vertices2);
						break;
					}
				}
			}
			if (soloPressed) {
				if (listeningSocket != INVALID_SOCKET) {
					closesocket(listeningSocket);
					listeningSocket = INVALID_SOCKET;
					WSACleanup();
					waitingForClient = false;
				}
				GAMESTATE = DEBUG;

				cout << "debug/singleplayer mode >>" << endl;
				for (int i = 0; i < STARTING_PARAMETER_COUNT; i++) {
					positions[i] = defaultServerPos[i];
				}
				for (int i = 0; i < STARTING_PARAMETER_COUNT; i++) {
					otherPlayerPositions[i] = defaultClientPos[i];
				}
				updatePlayer(positions, vertices1);
				updatePlayer(otherPlayerPositions, vertices2);

				break;
			}

			// text box input
			if (IPTextBox.isClicked(window)) {
				IPTextBox.message = "";
				textBoxEditMode = true;
			}
			else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
				textBoxEditMode = false;
			}
			if (textBoxEditMode) IPTextBox.collectInput(window);

			renderer.renderFrame();

			if (drawingBoard.saveRequested) {
				glFinish();
				glReadBuffer(GL_BACK);

				float leftNDC = drawingBoard.xpos - 0.5f * (drawingBoard.width / SCREEN_X);
				float rightNDC = drawingBoard.xpos + 0.5f * (drawingBoard.width / SCREEN_X);
				float bottomNDC = drawingBoard.ypos - 0.5f * (drawingBoard.height / SCREEN_Y);
				float topNDC = drawingBoard.ypos + 0.5f * (drawingBoard.height / SCREEN_Y);

				int leftPx = static_cast<int>((leftNDC + 1.0f) * 0.5f * SCREEN_X);
				int rightPx = static_cast<int>((rightNDC + 1.0f) * 0.5f * SCREEN_X);
				int bottomPx = static_cast<int>((bottomNDC + 1.0f) * 0.5f * SCREEN_Y);
				int topPx = static_cast<int>((topNDC + 1.0f) * 0.5f * SCREEN_Y);

				captureRegion(leftPx, bottomPx, rightPx - leftPx, topPx - bottomPx, drawingBoard.saveAs);

				drawingBoard.saved = true;
				drawingBoard.saveRequested = false;
			}


			glfwSwapBuffers(window);
			glfwPollEvents();
		}
		if (GAMESTATE == SERVER || GAMESTATE == CLIENT) {

			sendPlayerTexture(sock);
			newImageAvaliable = true;
			connected = true;
			//textureIDs[2] = loadTexture("textures/receivedPlayerTexture.png");
			//cout << "set received texture" << endl;
		}
		else {
			//cout << "loaded own texture" << endl;
			//textureIDs[2] = loadTexture("textures/playerTexture.png");
		}

		auto lastTime = std::chrono::high_resolution_clock::now();
		Text FPSText = Text(400, 0.8, 0.8, 4, "FPS: 0");
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

		// multiplayer loop
		while (!glfwWindowShouldClose(window) && (GAMESTATE == SERVER || GAMESTATE == CLIENT)) {

			glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			processInput(sock);
			if (!sendStatePacket(sock)) {
				cout << "connection lost..." << endl;
				break;
			}

			if (!network.recvData(sock, inBuff, IN_OUT_BUFF_SIZE, bytesReceived)) {
				cout << "Connection lost..." << endl;
				break;
			}
			handlePacket(inBuff, bytesReceived);

			for (int i = -0.5 * RAY_NUM; i < 0.5 * RAY_NUM; i++) { // cast rays
				drawRaycastLine(renderer, xpos + i, xpos, ypos, positions);
				drawMap(renderer);
			}

			// get angle to other player
			float angleToOther = atan2f((otherPlayerPositions[0] - positions[0]), (otherPlayerPositions[1] - positions[1]));
			float angleToOtherDegrees = angleToOther * (180.0f / 3.14159f);
			float relativeAngle = angleToOtherDegrees - xpos;
			if (relativeAngle < -180.0f) relativeAngle += 360.0f;
			if (relativeAngle > 180.0f) relativeAngle -= 360.0f;

			bool onScreen = drawPlayer(renderer, relativeAngle, angleToOther, ypos, otherPlayerPositions, positions, hitCount); // check if we can and draw other player

			if (shooting && isCrosshairOverPlayer(xpos, ypos, positions, otherPlayerPositions) && onScreen) {
				shooting = false;
				hitCount++;
				if (hitCount > maxHealth) {
					hitCount = 0;
				}
			}
			drawCrosshair(renderer, Renderer::ColorRGB());

			drawPlayerQuad(renderer, positions, Renderer::ColorRGB());
			drawPlayerQuad(renderer, otherPlayerPositions, Renderer::ColorRGB());

			FPSText.message = "FPS: " + std::to_string(int(1.0f / deltaTime));
			FPSText.pushToRenderer(&renderer);

			renderer.renderFrame();

			glfwSwapBuffers(window);
			glfwPollEvents();

			shooting = false;
			ZeroMemory(inBuff, IN_OUT_BUFF_SIZE);

			auto currentTime = std::chrono::high_resolution_clock::now();
			std::chrono::duration<float> elapsed = currentTime - lastTime;
			deltaTime = elapsed.count();
			lastTime = currentTime;

			if (newImageAvaliable) {
				textureIDs[2] = loadTexture("textures/receivedPlayerTexture.png");
				newImageAvaliable = false;
			}
		}
		// debug loop
		while (!glfwWindowShouldClose(window) && (GAMESTATE == DEBUG)) {

			glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			for (int i = -0.5 * RAY_NUM; i < 0.5 * RAY_NUM; i++) { // cast rays
				drawRaycastLine(renderer, xpos + i, xpos, ypos, positions);
				drawMap(renderer);
			}

			// angle to other player
			float angleToOther = atan2f((otherPlayerPositions[0] - positions[0]), (otherPlayerPositions[1] - positions[1]));
			float angleToOtherDegrees = angleToOther * (180.0f / 3.14159f);
			float relativeAngle = angleToOtherDegrees - xpos;
			if (relativeAngle < -180.0f) relativeAngle += 360.0f;
			if (relativeAngle > 180.0f) relativeAngle -= 360.0f;

			bool onScreen = drawPlayer(renderer, relativeAngle, angleToOther, ypos, otherPlayerPositions, positions, hitCount);

			if (shooting && isCrosshairOverPlayer(xpos, ypos, positions, otherPlayerPositions) && onScreen) {
				shooting = false;
				hitCount++;
				if (hitCount > maxHealth) {
					GAMESTATE = WIN;
					msg = "GOOD JOB!";
				}
			}
			drawCrosshair(renderer, Renderer::ColorRGB());

			drawPlayerQuad(renderer, positions, Renderer::ColorRGB());
			drawPlayerQuad(renderer, otherPlayerPositions, Renderer::ColorRGB());

			FPSText.message = "FPS: " + std::to_string(int(1.0f / deltaTime));
			FPSText.pushToRenderer(&renderer);

			renderer.renderFrame();

			glfwSwapBuffers(window);
			glfwPollEvents();

			processInput(sock);

			auto currentTime = std::chrono::high_resolution_clock::now();
			std::chrono::duration<float> elapsed = currentTime - lastTime;
			deltaTime = elapsed.count();
			lastTime = currentTime;
			//cout << "fps: " << 1.0f / deltaTime << endl;

		}

		// end menu
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
		while (!glfwWindowShouldClose(window) && (GAMESTATE == WIN || GAMESTATE == LOSS)) {
			if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
				glfwSetWindowShouldClose(window, true);
			}

			glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			if (connected) { // we dont really enforce
				sendStatePacket(sock);
				network.recvData(sock, inBuff, IN_OUT_BUFF_SIZE, bytesReceived);
				handlePacket(inBuff, bytesReceived);
			}

			Text instrText(SCREEN_X, 0, 0.7f, 4, "PRESS 'C' TO GO BACK TO MAIN MENU");
			Text statusText(SCREEN_X, 0, 0.5f, 4, msg);
			instrText.pushToRenderer(&renderer);
			statusText.pushToRenderer(&renderer);

			if (glfwGetKey(window, GLFW_KEY_C) == GLFW_PRESS) {
				break;
				closesocket(sock);
				WSACleanup();
			}

			renderer.renderFrame();
			glfwSwapBuffers(window);
			glfwPollEvents();
		}
		GAMESTATE = START;
		msg = "";
		hitCount = 0;

	}
	shaderProgram.Delete();

	glfwDestroyWindow(window);
	glfwTerminate();

	closesocket(sock);
	WSACleanup();
	return 0;
}