diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 409f765..a4017de 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -279,7 +279,7 @@ int main(int argc, char **argv) pthread_cancel(sendingThread); // Close the session and socket: - gnutls_bye(tlsSession, GNUTLS_SHUT_RDWR); + gnutls_bye(tlsSession, GNUTLS_SHUT_WR); close(socketFileDesc); // Free the structs: @@ -302,3 +302,4 @@ int main(int argc, char **argv) // Say goodbye: slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", characterDelay); } + diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 6061482..37aae8e 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -8,12 +8,14 @@ #include #include #include +#include #include #include #include #include #include #include "misc/lists.h" +#include "misc/gamelogic.h" #include "misc/constants.h" #include "misc/playerdata.h" #include "misc/texteffects.h" @@ -27,6 +29,7 @@ int main() int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; + pthread_t gameLogicThread; int clientSockets[PLAYERCOUNT]; userMessage sendBuffer, receiveBuffer; playerInfo connectedPlayers[PLAYERCOUNT]; @@ -34,14 +37,22 @@ int main() struct sockaddr_in serverAddress, clientAddress; inputMessageQueue * inputQueue = createInputMessageQueue(); outputMessageQueue * outputQueue = createOutputMessageQueue(); + + // Initialize test areas: + areaNode * areas = createAreaList(createArea("Spawn - North", "A large area, mostly empty, as if the designer hadn't bothered to put anything in it, just yet.")); + addAreaNodeToList(areas, createArea("Spawn - South", "A strange, white void. You feel rather uncomfortable.")); + addAreaNodeToList(areas, createArea("Temple of Emacs", "A beautifully ornate statue of GNU is above you on a pedestal. Inscribed into the pillar, over and over, is the phrase \"M-x exalt\", in delicate gold letters. You can't help but be awestruck.")); + createPath(getAreaFromList(areas, 0), getAreaFromList(areas, 1), "To South Spawn", "To North Spawn"); + createPath(getAreaFromList(areas, 2), getAreaFromList(areas, 1), "Back to South Spawn", "Path to Enlightenment."); // Initialize playerdata: for (int index = 0; index < PLAYERCOUNT; index++) { sprintf(testString, "UNNAMED %d", index); strcpy(connectedPlayers[index].playerName, testString); + connectedPlayers[index].currentArea = getAreaFromList(areas, 0); } - + // Give an intro: Display the Silverkin Industries logo and splash text. slowPrint(logostring, 3000); slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.3\n", 5000); @@ -116,6 +127,16 @@ int main() } slowPrint("\tTLS Sessions Initialization is:\t\033[32;40mGREEN.\033[0m\n", 5000); + // Prepare the game logic thread: + gameLogicParameters * gameLogicThreadParameters = malloc(sizeof(gameLogicParameters)); + gameLogicThreadParameters->connectedPlayers = connectedPlayers; + gameLogicThreadParameters->playerCount = &clientsAmount; + gameLogicThreadParameters->outputQueue = outputQueue; + gameLogicThreadParameters->inputQueue = inputQueue; + pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters); + + struct timeval timeout = {0, 500}; + while(keepRunning) { // Clear the set of file descriptors and add the master socket: @@ -142,7 +163,7 @@ int main() } // See if a connection is ready to be interacted with: - activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); + activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout); // Check if select() worked: if ((activityCheck < 0) && (errno != EINTR)) @@ -188,9 +209,10 @@ int main() if(FD_ISSET(socketCheck, &connectedClients)) { - if(messageReceive(tlssessions[index], &receiveBuffer) == -10) + int returnVal = messageReceive(tlssessions[index], &receiveBuffer); + if(returnVal == -10 || returnVal == 0) { - gnutls_bye(tlssessions[index], GNUTLS_SHUT_RDWR); + gnutls_bye(tlssessions[index], GNUTLS_SHUT_WR); gnutls_deinit(tlssessions[index]); shutdown(clientSockets[index], 2); close(clientSockets[index]); @@ -209,23 +231,46 @@ int main() } } // TEMPORARY: MOVE INPUT MESSAGES TO OUTPUT MESSAGES: - while(inputQueue->currentLength > 0) - { - inputMessage * message = peekInputMessage(inputQueue); - strncpy(message->content->senderName, message->sender->playerName, 32); - userInputSanatize(message->content->messageContent, MAX); - if(message->content->messageContent[0] != '\n') - { - queueOutputMessage(outputQueue, *message->content); - } - dequeueInputMessage(inputQueue); - } - while(outputQueue->currentLength > 0) + /* while(inputQueue->currentLength > 0) */ + /* { */ + /* inputMessage * message = peekInputMessage(inputQueue); */ + /* strncpy(message->content->senderName, message->sender->playerName, 32); */ + /* userInputSanatize(message->content->messageContent, MAX); */ + /* if(message->content->messageContent[0] != '\n') */ + /* { */ + /* queueOutputMessage(outputQueue, *message->content); */ + /* } */ + /* dequeueInputMessage(inputQueue); */ + /* } */ + + while(outputQueue->currentLength != 0) { + while(outputQueue->lock); + outputQueue->lock = true; outputMessage * message = peekOutputMessage(outputQueue); - for (int index = 0; index < PLAYERCOUNT; index++) + outputQueue->lock = false; + if(message->targets[0] == NULL) { - messageSend(tlssessions[index], message->content); + for (int index = 0; index < PLAYERCOUNT; index++) + { + messageSend(tlssessions[index], message->content); + } + } + else + { + int targetIndex = 0; + for(int index = 0; index < PLAYERCOUNT; index++) + { + if(message->targets[targetIndex] == NULL) + { + break; + } + if(&connectedPlayers[index] == message->targets[targetIndex]) + { + targetIndex++; + messageSend(tlssessions[index], message->content); + } + } } dequeueOutputMessage(outputQueue); } diff --git a/src/misc/gamelogic.c b/src/misc/gamelogic.c new file mode 100644 index 0000000..15efcd5 --- /dev/null +++ b/src/misc/gamelogic.c @@ -0,0 +1,95 @@ +// gamelogic.c: Contains function definitons for dealing with the game's logic. +// Barry Kane, 2022. +#include +#include "constants.h" +#include "gamelogic.h" +#include "playerdata.h" +#include "inputoutput.h" + +// ======================= +// -=[ Main Game Loop ]=-: +// ======================= + +// Thread function which runs the main game loop, given the needed parameters: +void * gameLogicLoop(void * parameters) +{ + gameLogicParameters * threadParameters = parameters; + inputMessage * currentInput = NULL; + bool keepRunning = true; + while(keepRunning) + { + if(threadParameters->inputQueue->currentLength != 0) + { + while(threadParameters->inputQueue->lock == true) + { + threadParameters->inputQueue->lock = true; + } + currentInput = peekInputMessage(threadParameters->inputQueue); + userInputSanatize(currentInput->content->messageContent, MAX); + if(currentInput->content->messageContent[0] == '/') + { + // TODO: Implement Command Queue. + // For now, basic intepretation will do. + if(strncmp(¤tInput->content->messageContent[1], "EXIT", 4) == 0) + { + userMessage * exitMessage = malloc(sizeof(userMessage)); + exitMessage->senderName[0] = '\0'; + exitMessage->messageContent[0] = '\0'; + queueTargetedOutputMessage(threadParameters->outputQueue, exitMessage, ¤tInput->sender, 1); + free(exitMessage); + } + if(strncmp(¤tInput->content->messageContent[1], "MOVE", 4) == 0) + { + userMessage * moveMessage = malloc(sizeof(userMessage)); + char requestedPath[32]; + strncpy(requestedPath, ¤tInput->content->messageContent[6], 32); + userInputSanatize(requestedPath, 32); + // Remove newlines: + for (int index = 0; index < 32; index++) + { + if (requestedPath[index] == '\n') + { + requestedPath[index] = '\0'; + } + } + requestedPath[31] = '\0'; + if(movePlayerToArea(currentInput->sender, requestedPath) == 0) + { + strcpy(moveMessage->senderName, "\0"); + strcpy(moveMessage->messageContent, currentInput->sender->currentArea->areaDescription); + queueTargetedOutputMessage(threadParameters->outputQueue, moveMessage, ¤tInput->sender, 1); + } + free(moveMessage); + } + } + else + { + strncpy(currentInput->content->senderName, currentInput->sender->playerName, 32); + // Create an array of players in the same area to receive the message: + playerInfo ** recipients = malloc(sizeof(playerInfo*) * *threadParameters->playerCount); + for(int index = 0; index < *threadParameters->playerCount; index++) + { + recipients[index] = NULL; + } + int recipientCount = 0; + for(int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++) + { + if(threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea) + { + recipients[recipientCount] = &threadParameters->connectedPlayers[playerIndex]; + recipientCount++; + } + } + if(currentInput->content->messageContent[0] != '\n') + { + queueTargetedOutputMessage(threadParameters->outputQueue, currentInput->content, recipients, recipientCount); + } + free(recipients); + } + currentInput = NULL; + threadParameters->inputQueue->lock = false; + dequeueInputMessage(threadParameters->inputQueue); + } + } + return NULL; +} diff --git a/src/misc/gamelogic.h b/src/misc/gamelogic.h new file mode 100644 index 0000000..be391ae --- /dev/null +++ b/src/misc/gamelogic.h @@ -0,0 +1,26 @@ +// gamelogic.h: Header file contatning function prototypes and datastructures +// for dealing with the game's logic. +// Barry Kane, 2022. +#ifndef GAMELOGIC_H +#define GAMELOGIC_H +#include "constants.h" +#include "playerdata.h" +#include "inputoutput.h" + +// ======================= +// -=[ Main Game Loop ]=-: +// ======================= + +// A datastructure containing the needed parameters for a main game loop: +typedef struct gameLogicParameters +{ + int * playerCount; + playerInfo * connectedPlayers; + inputMessageQueue * inputQueue; + outputMessageQueue * outputQueue; +} gameLogicParameters; + +// Thread function which runs the main game loop, given the needed parameters: +void * gameLogicLoop(void * parameters); + +#endif diff --git a/src/misc/inputoutput.c b/src/misc/inputoutput.c index 0bd1fc6..c6f0739 100644 --- a/src/misc/inputoutput.c +++ b/src/misc/inputoutput.c @@ -57,17 +57,21 @@ outputMessageQueue * createOutputMessageQueue(void) int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) { // Copy the message into a new output message: - outputMessage * outputMessage = malloc(sizeof(outputMessage)); + outputMessage * newOutputMessage = malloc(sizeof(outputMessage)); // Allocate the internal userMessage to store the message: - outputMessage->content = malloc(sizeof(userMessage)); + newOutputMessage->content = malloc(sizeof(userMessage)); + // Allocate the internal strings to store the message: + //outputMessage->content->senderName = malloc(sizeof(char)*32); + //outputMessage->content->messageContent = malloc(sizeof(char)*MAX); + // Copy the userMessage to the internal userMessage: - strncpy(outputMessage->content->senderName, messageToQueue.senderName, 32); - strncpy(outputMessage->content->messageContent, messageToQueue.messageContent, MAX); + strncpy(newOutputMessage->content->senderName, messageToQueue.senderName, 32); + strncpy(newOutputMessage->content->messageContent, messageToQueue.messageContent, MAX); // We have no targets, NULL sends to all players in an area: - outputMessage->targets[0] = NULL; + newOutputMessage->targets[0] = NULL; // Wait for the queue to unlock: while (queue->lock); @@ -87,24 +91,85 @@ int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) // If the queue is empty, set the first message as both the front and back of the queue: if(queue->front == NULL) { - queue->front = outputMessage; - queue->back = outputMessage; + queue->front = newOutputMessage; + queue->back = newOutputMessage; queue->currentLength++; // Unlock the queue: queue->lock = false; - return 0; } else { - queue->back->next = outputMessage; - queue->back = outputMessage; + queue->back->next = newOutputMessage; + queue->back = newOutputMessage; queue->currentLength++; // Unlock the queue: queue->lock = false; + return 0; + } + } +} +int queueTargetedOutputMessage(outputMessageQueue * queue, + userMessage * messageToQueue, playerInfo ** targets, int numberOfTargets) +{ + // Copy the message into a new output message: + outputMessage * newOutputMessage = malloc(sizeof(outputMessage)); + + // Allocate the internal userMessage to store the message: + newOutputMessage->content = malloc(sizeof(userMessage)); + + // Set the appropriate recipients: + for(int index = 0; index < numberOfTargets && index < PLAYERCOUNT; index++) + { + newOutputMessage->targets[index] = targets[index]; + } + for(int index = numberOfTargets; index < PLAYERCOUNT; index++) + { + newOutputMessage->targets[index] = NULL; + } + + // Copy the userMessage to the internal userMessage: + strncpy(newOutputMessage->content->senderName, messageToQueue->senderName, 32); + strncpy(newOutputMessage->content->messageContent, messageToQueue->messageContent, MAX); + + + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check that we're not overflowing the queue: + if ((queue->currentLength + 1) > MAXQUEUELENGTH) + { + // Unlock the queue: + queue->lock = false; + return -1; + } + else + { + // If the queue is empty, set the first message as both the front and back of the queue: + if(queue->front == NULL) + { + queue->front = newOutputMessage; + queue->back = newOutputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + return 0; + } + else + { + queue->back->next = newOutputMessage; + queue->back = newOutputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; return 0; } } @@ -213,8 +278,8 @@ int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, pla strncpy(inputMessage->content->messageContent, messageToQueue.messageContent, MAX); // We have no targets, NULL sends to all players in an area: - inputMessage->sender = sendingPlayer; - + inputMessage->sender = sendingPlayer; + // Wait for the queue to unlock: while (queue->lock); diff --git a/src/misc/inputoutput.h b/src/misc/inputoutput.h index 0726a01..00a05a9 100644 --- a/src/misc/inputoutput.h +++ b/src/misc/inputoutput.h @@ -17,9 +17,9 @@ typedef struct userMessage char messageContent[MAX]; } userMessage; -// ================= -// -=[Message I/O]=- -// ================= +// ================== +// -=[Message I/O]=-: +// ================== // Sends a message to a given TLS session, wraps the calls to gnutls_write: int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); @@ -52,8 +52,8 @@ outputMessageQueue * createOutputMessageQueue(void); // Enqueue a userMessage to an outputMessageQueue: int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue); -// int queueOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue, -// playerInfo * targets, int numberOfTargets); +int queueTargetedOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue, + playerInfo ** targets, int numberOfTargets); // Dequeue the front outputMessage from an outputMessageQueue: int dequeueOutputMessage(outputMessageQueue * queue); @@ -93,9 +93,9 @@ int dequeueInputMessage(inputMessageQueue * queue); // Return the front inputMessage from an inputMessageQueue: inputMessage * peekInputMessage(inputMessageQueue * queue); -// ====================== -// -=[Input Sanitation]=- -// ====================== +// ======================= +// -=[Input Sanitation]=-: +// ======================= // Sanatize user input to ensure it's okay to send to the server: void userInputSanatize(char * inputString, int length);