From 2c093903a4f5c32a659f085922f9cab28dd8a2b0 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Tue, 17 Aug 2021 18:57:56 +0100 Subject: [PATCH 01/53] Git Sanity Check --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 606bbff..0906675 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +CC = gcc clientsrc = $(wildcard src/misc/*.c) \ src/SilverMUDClient.c clientobj = $(clientsrc:.c=.o) From 849a80bd377ffad8c3f4cad4880540d45c36173c Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Thu, 19 Aug 2021 23:07:58 +0100 Subject: [PATCH 02/53] Basic input sanatization: Created new library to deal with user input. Implemented check in client to prevent C-d spamming the server. C-d now exits. Implemented check in client to prevent clients sending messages containing only newlines to the server. --- src/SilverMUDClient.c | 24 +++++++++++++++--------- src/SilverMUDServer.c | 10 +++++----- src/misc/inputhandling.c | 16 ++++++++++++++++ src/misc/inputhandling.h | 10 ++++++++++ src/misc/texteffects.c | 2 +- src/misc/texteffects.h | 4 +++- 6 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 src/misc/inputhandling.c create mode 100644 src/misc/inputhandling.h diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index ea264fc..9972908 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -7,6 +7,7 @@ #include #include #include "misc/texteffects.h" +#include "misc/inputhandling.h" #define MAX 1024 #define PORT 5000 #define SA struct sockaddr @@ -20,11 +21,16 @@ void * messageSender(void * sockfd) { bzero(sendBuffer, MAX); printf("COMM-LINK> "); - fgets(sendBuffer, MAX, stdin); - if(sendBuffer[0] != '\n'); + if(fgets(sendBuffer, MAX, stdin) == NULL) { - write((long)sockfd, sendBuffer, MAX); + exit(0); } + userInputSanatize(sendBuffer, MAX); + if(sendBuffer[0] == '\n') + { + continue; + } + write((long)sockfd, sendBuffer, MAX); } } @@ -35,9 +41,9 @@ void * messageReceiver(void * sockfd) while (1) { read((long)sockfd, receiveBuffer, MAX); - slowprint("\nUSER-MESSAGE: ", 8000); - slowprint(receiveBuffer, 8000); - slowprint("\nCOMM-LINK (CONT.)> ", 8000); + slowPrint("\nUSER-MESSAGE: ", 8000); + slowPrint(receiveBuffer, 8000); + slowPrint("\nCOMM-LINK (CONT.)> ", 8000); bzero(receiveBuffer, MAX); } } @@ -57,7 +63,7 @@ int main(int argc, char **argv) } else { - slowprint("Socket successfully created.\n", 8000); + slowPrint("Socket successfully created.\n", 8000); } bzero(&servaddr, sizeof(servaddr)); @@ -76,12 +82,12 @@ int main(int argc, char **argv) // Connect the server and client sockets, Kronk: if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) { - slowprint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", 8000); + slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", 8000); exit(0); } else { - slowprint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); + slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); } // Run a thread to send messages, and use main to recieve. diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index df4783c..0fb1c33 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -27,8 +27,8 @@ int main() struct sockaddr_in serverAddress, clientAddress; // 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.1\n", 5000); + slowPrint(logostring, 3000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.1\n", 5000); // Initialize the sockets to 0, so we don't crash. for (int index = 0; index < maxClients; index++) @@ -46,7 +46,7 @@ int main() else { - slowprint(" Socket creation is \033[32;40mGREEN.\033[0m\n", 5000); + slowPrint(" Socket creation is \033[32;40mGREEN.\033[0m\n", 5000); } bzero(&serverAddress, sizeof(serverAddress)); @@ -64,7 +64,7 @@ int main() } else { - slowprint(" Socket binding is \033[32;40mGREEN.\033[0m\n", 5000); + slowPrint(" Socket binding is \033[32;40mGREEN.\033[0m\n", 5000); } // Let's start listening: @@ -75,7 +75,7 @@ int main() } else { - slowprint(" Server listening is \033[32;40mGREEN.\033[0m\n", 5000); + slowPrint(" Server listening is \033[32;40mGREEN.\033[0m\n", 5000); } length = sizeof(clientAddress); diff --git a/src/misc/inputhandling.c b/src/misc/inputhandling.c new file mode 100644 index 0000000..0f4824b --- /dev/null +++ b/src/misc/inputhandling.c @@ -0,0 +1,16 @@ +// inputhandling.c: Implementation of input handling library for SilverMUD. +// Barry Kane, 2021. +#include + +void userInputSanatize(char * inputString, int length) +{ + for(int index = 0; index <= length; index++) + { + if(!isprint(inputString[index])) + { + inputString[index] = '\n'; + inputString[index + 1] = '\0'; + break; + } + } +} diff --git a/src/misc/inputhandling.h b/src/misc/inputhandling.h new file mode 100644 index 0000000..95c9bca --- /dev/null +++ b/src/misc/inputhandling.h @@ -0,0 +1,10 @@ +// inputhandling.h: Header file for the inputhandling library for SilverMUD. +// Barry Kane, 2021 +#ifndef INPUTHANDLING_H +#define INPUTHANDLING_H +#include + +// Sanatize user input to ensure it's okay to send to the server: +void userInputSanatize(char * inputString, int length); + +#endif diff --git a/src/misc/texteffects.c b/src/misc/texteffects.c index 23f03b8..a1b7afd 100644 --- a/src/misc/texteffects.c +++ b/src/misc/texteffects.c @@ -3,7 +3,7 @@ #include #include -void slowprint(char * stringToPrint, int delay) +void slowPrint(char * stringToPrint, int delay) { int characterIndex = 0; while(stringToPrint[characterIndex] != '\0') diff --git a/src/misc/texteffects.h b/src/misc/texteffects.h index b04a84b..daa4c7c 100644 --- a/src/misc/texteffects.h +++ b/src/misc/texteffects.h @@ -1,9 +1,11 @@ +// texteffects.h: Header file for the texteffects library for SilverMUD. +// Barry Kane, 2021. #ifndef TEXTEFFECTS_H_ #define TEXTEFFECTS_H_ #include // A fancy, character by character print. Similar to a serial terminal with lower baud rate. -void slowprint(char * stringToPrint, int delay); +void slowPrint(char * stringToPrint, int delay); // A string containing an ASCII art version of the Silverkin Industries logo. char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; From 33bc9bcda0c5d4afbbfa9b5371ad2ef83b5e6f1b Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 3 Sep 2021 18:47:11 +0100 Subject: [PATCH 03/53] Adapted client to use Ncurses instead of raw terminal output: Created "slowPrintNcurses", which is a version of "slowPrint" compatible with Ncurses screens. Ncurses is now used in place of raw-terminal output. The screen clears after inital start-up messages. C-d no longer exits, and still doesn't spam. Added Ncurses to the ld options of client in the Makefile. Created ld options for server in the Makefile. --- Makefile | 6 +++--- src/SilverMUDClient.c | 20 ++++++++++++++------ src/SilverMUDServer.c | 1 + src/misc/texteffects.c | 14 ++++++++++++++ src/misc/texteffects.h | 3 +++ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 0906675..0bccd36 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,13 @@ clientobj = $(clientsrc:.c=.o) serversrc = $(wildcard src/misc/*.c) \ src/SilverMUDServer.c serverobj = $(serversrc:.c=.o) -CLIENTLDFLAGS= -lpthread - +CLIENTLDFLAGS= -lpthread -lncurses +SERVERLDFLAGS= -lncurses SilverMUDClient: $(clientobj) gcc -o $@ $^ $(CLIENTLDFLAGS) SilverMUDServer: $(serverobj) - gcc -o $@ $^ + gcc -o $@ $^ $(SERVERLDFLAGS) .PHONY: clean clean: diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 9972908..6dc243e 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include "misc/texteffects.h" @@ -20,8 +21,8 @@ void * messageSender(void * sockfd) while (1) { bzero(sendBuffer, MAX); - printf("COMM-LINK> "); - if(fgets(sendBuffer, MAX, stdin) == NULL) + wprintw(stdscr, "COMM-LINK> "); + if(wgetnstr(stdscr, sendBuffer, MAX) == ERR) { exit(0); } @@ -41,9 +42,9 @@ void * messageReceiver(void * sockfd) while (1) { read((long)sockfd, receiveBuffer, MAX); - slowPrint("\nUSER-MESSAGE: ", 8000); - slowPrint(receiveBuffer, 8000); - slowPrint("\nCOMM-LINK (CONT.)> ", 8000); + slowPrintNcurses("\nUSER-MESSAGE: ", 8000); + slowPrintNcurses(receiveBuffer, 8000); + slowPrintNcurses("\nCOMM-LINK (CONT.)> ", 8000); bzero(receiveBuffer, MAX); } } @@ -53,7 +54,6 @@ int main(int argc, char **argv) int sockfd, connfd; struct sockaddr_in servaddr, cli; pthread_t messagingThread; - // Give me a socket, and make sure it's working: sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) @@ -89,6 +89,11 @@ int main(int argc, char **argv) { slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); } + usleep(100000); + + // Setup Ncurses: + initscr(); + scrollok(stdscr, true); // Run a thread to send messages, and use main to recieve. pthread_create(&messagingThread, NULL, messageSender, (void *)(long)sockfd); @@ -96,4 +101,7 @@ int main(int argc, char **argv) // Close the socket. close(sockfd); + + // Unsetup Ncurses: + endwin(); } diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 0fb1c33..46d213c 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/src/misc/texteffects.c b/src/misc/texteffects.c index a1b7afd..0078dc3 100644 --- a/src/misc/texteffects.c +++ b/src/misc/texteffects.c @@ -2,6 +2,7 @@ // Barry Kane, 2021. #include #include +#include void slowPrint(char * stringToPrint, int delay) { @@ -15,3 +16,16 @@ void slowPrint(char * stringToPrint, int delay) characterIndex++; } } + +void slowPrintNcurses(char * stringToPrint, int delay) +{ + int characterIndex = 0; + while(stringToPrint[characterIndex] != '\0') + { + addch(stringToPrint[characterIndex]); + // Refresh the ncurses screen. + refresh(); + usleep(delay); + characterIndex++; + } +} diff --git a/src/misc/texteffects.h b/src/misc/texteffects.h index daa4c7c..6cb6729 100644 --- a/src/misc/texteffects.h +++ b/src/misc/texteffects.h @@ -7,6 +7,9 @@ // A fancy, character by character print. Similar to a serial terminal with lower baud rate. void slowPrint(char * stringToPrint, int delay); +// The same, altered to work with Ncurses. +void slowPrintNcurses(char * stringToPrint, int delay); + // A string containing an ASCII art version of the Silverkin Industries logo. char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; From 7047d0ee08dd522709d3130fa340d33f4ab5e23f Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 10 Sep 2021 15:03:02 +0100 Subject: [PATCH 04/53] Added two-window messaging to the client. Client now has two seperate Ncurses windows for sending and receiving. Added SIGINT handler which sets a global boolean to gracefully exit and free memory. Sending and Receiving are now on their own threads. A pointer-to-struct is now passed to the threads. The main thread will now wait to cancel the threads upon receiving SIGINT. slowPrintNcurses now takes a window argument. The server now doesn't check that a client receives the message that they sent, allowing for full chat history. --- src/SilverMUDClient.c | 99 ++++++++++++++++++++++++++++++++++-------- src/SilverMUDServer.c | 2 +- src/misc/texteffects.c | 7 +-- src/misc/texteffects.h | 3 +- 4 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 6dc243e..18a4a6a 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -13,47 +15,73 @@ #define PORT 5000 #define SA struct sockaddr -void * messageSender(void * sockfd) +// A struct for passing arguments to our threads containing a file descriptor and a window pointer: +typedef struct threadparameters { + int socketDescriptor; + WINDOW * window; +} threadparameters; + +// A globally availible exit boolean. +bool shouldExit = false; + +void sigintHandler(int signal) +{ + shouldExit = true; +} + +void * messageSender(void * parameters) +{ + // Takes user input in a window, sanatizes it, and sends it to the server: + struct threadparameters *threadParameters = parameters; char sendBuffer[MAX]; int characterindex; - - while (1) + + while (!shouldExit) { bzero(sendBuffer, MAX); - wprintw(stdscr, "COMM-LINK> "); - if(wgetnstr(stdscr, sendBuffer, MAX) == ERR) + wprintw(threadParameters->window, "\n\n\nCOMM-LINK> "); + if(wgetnstr(threadParameters->window, sendBuffer, MAX) == ERR) { - exit(0); + // Quit if there's any funny business with getting input: + pthread_exit(NULL); } userInputSanatize(sendBuffer, MAX); if(sendBuffer[0] == '\n') { continue; } - write((long)sockfd, sendBuffer, MAX); + write(threadParameters->socketDescriptor, sendBuffer, MAX); } + pthread_exit(NULL); } -void * messageReceiver(void * sockfd) +void * messageReceiver(void * parameters) { + // Takes messages from the server and prints them to the chat log window: + struct threadparameters *threadParameters = parameters; char receiveBuffer[MAX]; - while (1) + while (!shouldExit) { - read((long)sockfd, receiveBuffer, MAX); - slowPrintNcurses("\nUSER-MESSAGE: ", 8000); - slowPrintNcurses(receiveBuffer, 8000); - slowPrintNcurses("\nCOMM-LINK (CONT.)> ", 8000); + read(threadParameters->socketDescriptor, receiveBuffer, MAX); + slowPrintNcurses("USER-MESSAGE: ", 8000, threadParameters->window); + slowPrintNcurses(receiveBuffer, 8000, threadParameters->window); bzero(receiveBuffer, MAX); } + pthread_exit(NULL); } int main(int argc, char **argv) { int sockfd, connfd; struct sockaddr_in servaddr, cli; - pthread_t messagingThread; + pthread_t sendingThread; + pthread_t receivingThread; + + // Set the SIGINT handler. + signal(SIGINT, sigintHandler); + // Give me a socket, and make sure it's working: sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) @@ -93,15 +121,48 @@ int main(int argc, char **argv) // Setup Ncurses: initscr(); - scrollok(stdscr, true); + + // Create two pointers to structs to pass arguments to the threads: + threadparameters * logArea; + threadparameters * messageArea; - // Run a thread to send messages, and use main to recieve. - pthread_create(&messagingThread, NULL, messageSender, (void *)(long)sockfd); - messageReceiver((void *)(long)sockfd); + logArea = malloc(sizeof(*logArea)); + messageArea = malloc(sizeof(*messageArea)); + + // Make the windows for the structs, and pass the socket descriptor: + logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); + logArea->socketDescriptor = sockfd; + messageArea->window = newwin(3, COLS, LINES - 3, 0); + messageArea->socketDescriptor = sockfd; + + // Set the two windows to scroll: + scrollok(logArea->window, true); + scrollok(messageArea->window, true); - // Close the socket. + // Run a thread to send messages, and use main to recieve: + pthread_create(&sendingThread, NULL, messageSender, messageArea); + pthread_create(&receivingThread, NULL, messageReceiver, logArea); + + // Wait for SIGINT to change + while(!shouldExit) + { + sleep(250); + } + + // Close the threads: + pthread_cancel(sendingThread); + pthread_cancel(receivingThread); + + // Close the socket: close(sockfd); + // Free the structs: + free(logArea); + free(messageArea); + // Unsetup Ncurses: endwin(); + + // Say Goodbye: + slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", 8000); } diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 46d213c..32b7255 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -172,7 +172,7 @@ int main() fflush(stdout); for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) { - if(sendIndex != i && clientSockets[sendIndex] != STDIN_FILENO && clientSockets[sendIndex] != STDOUT_FILENO && clientSockets[sendIndex] != STDERR_FILENO) + if(clientSockets[sendIndex] != STDIN_FILENO && clientSockets[sendIndex] != STDOUT_FILENO && clientSockets[sendIndex] != STDERR_FILENO) { write(clientSockets[sendIndex], receiveBuffer, sizeof(receiveBuffer)); } diff --git a/src/misc/texteffects.c b/src/misc/texteffects.c index 0078dc3..875b3cc 100644 --- a/src/misc/texteffects.c +++ b/src/misc/texteffects.c @@ -17,15 +17,16 @@ void slowPrint(char * stringToPrint, int delay) } } -void slowPrintNcurses(char * stringToPrint, int delay) +void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window) { int characterIndex = 0; while(stringToPrint[characterIndex] != '\0') { - addch(stringToPrint[characterIndex]); + waddch(window, stringToPrint[characterIndex]); // Refresh the ncurses screen. - refresh(); + wrefresh(window); usleep(delay); characterIndex++; } + wrefresh(window); } diff --git a/src/misc/texteffects.h b/src/misc/texteffects.h index 6cb6729..d75e92c 100644 --- a/src/misc/texteffects.h +++ b/src/misc/texteffects.h @@ -3,12 +3,13 @@ #ifndef TEXTEFFECTS_H_ #define TEXTEFFECTS_H_ #include +#include // A fancy, character by character print. Similar to a serial terminal with lower baud rate. void slowPrint(char * stringToPrint, int delay); // The same, altered to work with Ncurses. -void slowPrintNcurses(char * stringToPrint, int delay); +void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window); // A string containing an ASCII art version of the Silverkin Industries logo. char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; From 94118039427c81e047424c73f2f6c3ccb2e88f94 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 10 Sep 2021 15:07:42 +0100 Subject: [PATCH 05/53] Increment version message for merge. Incremented the version number by 0.1 for the server. Added version splash to the client. --- src/SilverMUDClient.c | 5 ++++- src/SilverMUDServer.c | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 18a4a6a..bde2293 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -78,9 +78,12 @@ int main(int argc, char **argv) struct sockaddr_in servaddr, cli; pthread_t sendingThread; pthread_t receivingThread; - + // Set the SIGINT handler. signal(SIGINT, sigintHandler); + + // Print welcome message: + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.2\n", 5000); // Give me a socket, and make sure it's working: sockfd = socket(AF_INET, SOCK_STREAM, 0); diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 32b7255..3eb8724 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -29,7 +29,7 @@ int main() // 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.1\n", 5000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.2\n", 5000); // Initialize the sockets to 0, so we don't crash. for (int index = 0; index < maxClients; index++) From 18a4f416f6970bd826a6a5157cb03a61e1702048 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Sep 2021 00:07:13 +0100 Subject: [PATCH 06/53] Added basic name system - Added basic name system. - Added playerdata.h. - Added basic /NAME command. TODO: Create proper command system. - Added datastructures for user messages and user names. --- src/SilverMUDClient.c | 14 ++++++++----- src/SilverMUDServer.c | 49 +++++++++++++++++++++++++++++++++++++------ src/misc/playerdata.h | 17 +++++++++++++++ 3 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/misc/playerdata.h diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index bde2293..daca94a 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -9,6 +9,7 @@ #include #include #include +#include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputhandling.h" #define MAX 1024 @@ -61,13 +62,16 @@ void * messageReceiver(void * parameters) { // Takes messages from the server and prints them to the chat log window: struct threadparameters *threadParameters = parameters; - char receiveBuffer[MAX]; + userMessage receiveBuffer; while (!shouldExit) { - read(threadParameters->socketDescriptor, receiveBuffer, MAX); - slowPrintNcurses("USER-MESSAGE: ", 8000, threadParameters->window); - slowPrintNcurses(receiveBuffer, 8000, threadParameters->window); - bzero(receiveBuffer, MAX); + read(threadParameters->socketDescriptor, &receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); + read(threadParameters->socketDescriptor, &receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); + slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window); + slowPrintNcurses(": ", 8000, threadParameters->window); + slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); + bzero(receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); + bzero(receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); } pthread_exit(NULL); } diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 3eb8724..9433d3c 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -12,6 +12,7 @@ #include #include #include +#include "misc/playerdata.h" #include "misc/texteffects.h" const int PORT = 5000; const int MAX = 1024; @@ -23,10 +24,18 @@ int main() socketCheck, activityCheck, readLength; int clientSockets[64]; int maxClients = 64; + userMessage sendBuffer; char receiveBuffer[MAX]; fd_set connectedClients; - struct sockaddr_in serverAddress, clientAddress; + playerInfo connectedPlayers[64]; + struct sockaddr_in serverAddress, clientAddress; + // Initialize playerdata: + for (int index = 0; index < maxClients; index++) + { + strcpy(connectedPlayers[index].playerName, "UNNAMED"); + } + // 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.2\n", 5000); @@ -163,21 +172,49 @@ int main() // Close the socket and mark as 0 in list for reuse: close(socketCheck); clientSockets[i] = 0; - } - + } + // Name change command: Move logic to a command interpreter later: + else if (receiveBuffer[0] == '/') + { + char newName[32]; + if(strncmp(receiveBuffer, "/NAME", 5) == 0) + { + strncpy(newName, &receiveBuffer[6], 32); + // Remove newlines: + for (int index = 0; index < 32; index++) + { + if (newName[index] == '\n') + { + newName[index] = '\0'; + } + } + for (int index = 0; index < maxClients; index++) + { + if(strncmp(newName, connectedPlayers[index].playerName, 32) == 0) + { + break; + } + } + strncpy(connectedPlayers[i].playerName, newName, 32); + } + } // Echo back the message that came in: else { - printf("%d: %s", clientSockets[i], receiveBuffer); + printf("%d/%s: %s", clientSockets[i], connectedPlayers[i].playerName, receiveBuffer); fflush(stdout); + strcpy(sendBuffer.senderName, connectedPlayers[i].playerName); + strcpy(sendBuffer.messageContent, receiveBuffer); for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) { if(clientSockets[sendIndex] != STDIN_FILENO && clientSockets[sendIndex] != STDOUT_FILENO && clientSockets[sendIndex] != STDERR_FILENO) { - write(clientSockets[sendIndex], receiveBuffer, sizeof(receiveBuffer)); + write(clientSockets[sendIndex], sendBuffer.senderName, sizeof(sendBuffer.senderName)); + write(clientSockets[sendIndex], sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); } } - bzero(receiveBuffer, sizeof(receiveBuffer)); + bzero(sendBuffer.senderName, sizeof(sendBuffer.senderName)); + bzero(sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); } } } diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h new file mode 100644 index 0000000..4cf1d8c --- /dev/null +++ b/src/misc/playerdata.h @@ -0,0 +1,17 @@ +// playerdata.h: Header file containing data structures for player data and function +// definitions for interacting with said data. +// Barry Kane, 2021. + +typedef struct userMessage +{ + char senderName[32]; + char messageContent[1024]; +} userMessage; + +typedef struct playerInfo +{ + char playerName[32]; +} playerInfo; + + + From ae8373d4ce4cddd6632893623cd86d68eb12b84c Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Sep 2021 00:12:05 +0100 Subject: [PATCH 07/53] Incremented Version Number. - Incremented version number in preperation for merge. --- src/SilverMUDClient.c | 2 +- src/SilverMUDServer.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index daca94a..f64bb66 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -87,7 +87,7 @@ int main(int argc, char **argv) signal(SIGINT, sigintHandler); // Print welcome message: - slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.2\n", 5000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.3\n", 5000); // Give me a socket, and make sure it's working: sockfd = socket(AF_INET, SOCK_STREAM, 0); diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 9433d3c..c2bde11 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.1. +// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.3. // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -38,7 +38,7 @@ int main() // 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.2\n", 5000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.3\n", 5000); // Initialize the sockets to 0, so we don't crash. for (int index = 0; index < maxClients; index++) From 85a31a293390ee88fd462a68c26681f089474085 Mon Sep 17 00:00:00 2001 From: Barry Date: Thu, 21 Oct 2021 21:58:55 +0100 Subject: [PATCH 08/53] Added basic area system - Added playerdata.c - Added basic move command - Added a basic initialisation of two connected rooms - Added datastructures for areas and paths --- src/SilverMUD.org | 8 ++++++++ src/SilverMUDServer.c | 38 ++++++++++++++++++++++++++++++++------ src/misc/playerdata.c | 19 +++++++++++++++++++ src/misc/playerdata.h | 21 +++++++++++++++++++-- 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 src/SilverMUD.org create mode 100644 src/misc/playerdata.c diff --git a/src/SilverMUD.org b/src/SilverMUD.org new file mode 100644 index 0000000..4d31210 --- /dev/null +++ b/src/SilverMUD.org @@ -0,0 +1,8 @@ +* Game Design +** The gameplay: +- Each player may travel to areas connected to the current one, depending on flags. +- Players may perform actions, success depending on their stats. +- Players may create characters. +- A GM may create content using GNU Guile. +* Implementation Plans +* TODOs diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index c2bde11..86cf7bb 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -25,15 +25,28 @@ int main() int clientSockets[64]; int maxClients = 64; userMessage sendBuffer; + playerArea areaA, areaB; + playerPath pathA, pathB; char receiveBuffer[MAX]; fd_set connectedClients; playerInfo connectedPlayers[64]; struct sockaddr_in serverAddress, clientAddress; - // Initialize playerdata: + // Initialize areas: + strncpy(areaA.areaName, "Spawn - North", 32); + strncpy(areaB.areaName, "Spawn - South", 32); + strncpy(pathA.pathName, "To South Spawn", 32); + strncpy(pathB.pathName, "To North Spawn", 32); + pathA.areaToJoin = &areaB; + pathB.areaToJoin = &areaA; + areaA.areaExits[0] = &pathA; + areaB.areaExits[0] = &pathB; + + // Initialize playerdata: for (int index = 0; index < maxClients; index++) { strcpy(connectedPlayers[index].playerName, "UNNAMED"); + connectedPlayers[index].currentArea = &areaA; } // Give an intro: Display the Silverkin Industries logo and splash text. @@ -89,7 +102,6 @@ int main() } length = sizeof(clientAddress); - //connectionFileDesc = accept(socketFileDesc, (sockaddr*)&clientAddress, &length); // Accept the data packet from client and verification while (1) { @@ -175,10 +187,10 @@ int main() } // Name change command: Move logic to a command interpreter later: else if (receiveBuffer[0] == '/') - { - char newName[32]; + { if(strncmp(receiveBuffer, "/NAME", 5) == 0) { + char newName[32]; strncpy(newName, &receiveBuffer[6], 32); // Remove newlines: for (int index = 0; index < 32; index++) @@ -197,17 +209,31 @@ int main() } strncpy(connectedPlayers[i].playerName, newName, 32); } + else if(strncmp(receiveBuffer, "/MOVE", 5) == 0) + { + char requestedPath[32]; + strncpy(requestedPath, &receiveBuffer[6], 32); + // Remove newlines: + for (int index = 0; index < 32; index++) + { + if (requestedPath[index] == '\n') + { + requestedPath[index] = '\0'; + } + } + movePlayerToArea(&connectedPlayers[i], requestedPath); + } } // Echo back the message that came in: else { - printf("%d/%s: %s", clientSockets[i], connectedPlayers[i].playerName, receiveBuffer); + printf("%d/%s/%s: %s", clientSockets[i], connectedPlayers[i].currentArea->areaName, connectedPlayers[i].playerName, receiveBuffer); fflush(stdout); strcpy(sendBuffer.senderName, connectedPlayers[i].playerName); strcpy(sendBuffer.messageContent, receiveBuffer); for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) { - if(clientSockets[sendIndex] != STDIN_FILENO && clientSockets[sendIndex] != STDOUT_FILENO && clientSockets[sendIndex] != STDERR_FILENO) + if(clientSockets[sendIndex] != STDIN_FILENO && (connectedPlayers[i].currentArea == connectedPlayers[sendIndex].currentArea)) { write(clientSockets[sendIndex], sendBuffer.senderName, sizeof(sendBuffer.senderName)); write(clientSockets[sendIndex], sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); diff --git a/src/misc/playerdata.c b/src/misc/playerdata.c new file mode 100644 index 0000000..b8279ad --- /dev/null +++ b/src/misc/playerdata.c @@ -0,0 +1,19 @@ +// playerdata.c: Contains functions definitions for working with player data. +// Barry Kane, 2021 +#include +#include "playerdata.h" + +// Move a player to a different area given a path in the area. +int movePlayerToArea(playerInfo * player, char * requestedPath) +{ + for (int index = 0; index < 32; index++) + { + if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) + { + player->currentArea = player->currentArea->areaExits[index]->areaToJoin; + return 0; + } + } + return 1; +} + diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h index 4cf1d8c..b986f53 100644 --- a/src/misc/playerdata.h +++ b/src/misc/playerdata.h @@ -6,12 +6,29 @@ typedef struct userMessage { char senderName[32]; char messageContent[1024]; + } userMessage; +typedef struct playerPath playerPath; +typedef struct playerArea playerArea; + +struct playerPath +{ + char pathName[32]; + playerArea * areaToJoin; +}; + +struct playerArea +{ + char areaName[32]; + playerPath * areaExits[32]; +}; + typedef struct playerInfo { char playerName[32]; + playerArea * currentArea; } playerInfo; - - +// Move a player to a different area given a path in the area. +int movePlayerToArea(playerInfo * player, char * requestedPath); From 241ac7a92b6a335d780486f3c23a3b8385e3d408 Mon Sep 17 00:00:00 2001 From: Barry Date: Thu, 4 Nov 2021 23:14:47 +0000 Subject: [PATCH 09/53] Added area and path creation functions - Added missing header guards. - Increased the size of message contents to 2048. - Added area and path initialization functions. - movePlayerToArea no longer segfaults. - /LOOK added to allow players to find exits. - Amount of paths allowed out of an area has been decreased to 16. - Debug builds are now available from the Makefile. - Removed unused variables. - Input sanatization has been moved to the server-side, phew. - Server messages are now displayed differently to player messages. - New area initialization has been added until I can integrate Guile. - Server's sendBuffer has been renamed messageBuffer. - Areas now have descriptions. - Descriptions are sent to the player upon joining an area and /LOOK-ing. --- Makefile | 15 ++++-- src/SilverMUDClient.c | 25 ++++++--- src/SilverMUDServer.c | 108 ++++++++++++++++++++++++++------------- src/misc/inputhandling.c | 2 + src/misc/playerdata.c | 69 ++++++++++++++++++++++--- src/misc/playerdata.h | 18 +++++-- 6 files changed, 181 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index 0bccd36..3d4b140 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,22 @@ serverobj = $(serversrc:.c=.o) CLIENTLDFLAGS= -lpthread -lncurses SERVERLDFLAGS= -lncurses SilverMUDClient: $(clientobj) - gcc -o $@ $^ $(CLIENTLDFLAGS) + gcc -s -O3 -o $@ $^ $(CLIENTLDFLAGS) SilverMUDServer: $(serverobj) - gcc -o $@ $^ $(SERVERLDFLAGS) + gcc -s -O3 -o $@ $^ $(SERVERLDFLAGS) + +SilverMUDClientDebug: $(clientobj) + gcc -ggdb -Wall $^ $(CLIENTLDFLAGS) -o $@ + +SilverMUDServerDebug: $(serverobj) + gcc -ggdb -Wall $^ $(SERVERLDFLAGS) -o $@ .PHONY: clean clean: - rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer + rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug all: SilverMUDClient SilverMUDServer + +debug: CFLAGS += -Wall -ggdb +debug: SilverMUDClientDebug SilverMUDServerDebug diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index f64bb66..0e1e197 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -12,7 +12,7 @@ #include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputhandling.h" -#define MAX 1024 +#define MAX 2048 #define PORT 5000 #define SA struct sockaddr @@ -36,7 +36,6 @@ void * messageSender(void * parameters) // Takes user input in a window, sanatizes it, and sends it to the server: struct threadparameters *threadParameters = parameters; char sendBuffer[MAX]; - int characterindex; while (!shouldExit) { @@ -47,7 +46,6 @@ void * messageSender(void * parameters) // Quit if there's any funny business with getting input: pthread_exit(NULL); } - userInputSanatize(sendBuffer, MAX); if(sendBuffer[0] == '\n') { continue; @@ -67,9 +65,20 @@ void * messageReceiver(void * parameters) { read(threadParameters->socketDescriptor, &receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); read(threadParameters->socketDescriptor, &receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); - slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window); - slowPrintNcurses(": ", 8000, threadParameters->window); - slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); + + if(receiveBuffer.senderName[0] == '\0') + { + slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); + slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); + slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); + } + else + { + slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window); + slowPrintNcurses(": ", 8000, threadParameters->window); + slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); + } + bzero(receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); bzero(receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); } @@ -78,8 +87,8 @@ void * messageReceiver(void * parameters) int main(int argc, char **argv) { - int sockfd, connfd; - struct sockaddr_in servaddr, cli; + int sockfd; + struct sockaddr_in servaddr; pthread_t sendingThread; pthread_t receivingThread; diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 86cf7bb..f6b1dd5 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -14,39 +14,35 @@ #include #include "misc/playerdata.h" #include "misc/texteffects.h" +#include "misc/inputhandling.h" const int PORT = 5000; -const int MAX = 1024; typedef struct sockaddr sockaddr; int main() -{ +{ int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, readLength; int clientSockets[64]; int maxClients = 64; - userMessage sendBuffer; - playerArea areaA, areaB; - playerPath pathA, pathB; - char receiveBuffer[MAX]; + userMessage messageBuffer; + char receiveBuffer[2048]; fd_set connectedClients; + playerArea * areaA, * areaB, * areaC; playerInfo connectedPlayers[64]; struct sockaddr_in serverAddress, clientAddress; - // Initialize areas: - strncpy(areaA.areaName, "Spawn - North", 32); - strncpy(areaB.areaName, "Spawn - South", 32); - strncpy(pathA.pathName, "To South Spawn", 32); - strncpy(pathB.pathName, "To North Spawn", 32); - pathA.areaToJoin = &areaB; - pathB.areaToJoin = &areaA; - areaA.areaExits[0] = &pathA; - areaB.areaExits[0] = &pathB; + // Initialize areas: + areaA = createArea("Spawn - North", "A large area, mostly empty, as if the designer hadn't bothered to put anything in it, just yet."); + areaB = createArea("Spawn - South", "A strange, white void. You feel rather uncomfortable."); + areaC = 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(areaA, areaB, "To South Spawn", "To North Spawn"); + createPath(areaC, areaB, "Back to South Spawn", "Path to Enlightenment."); - // Initialize playerdata: - for (int index = 0; index < maxClients; index++) + // Initialize playerdata: + for (int index = 0; index < maxClients; index++) { strcpy(connectedPlayers[index].playerName, "UNNAMED"); - connectedPlayers[index].currentArea = &areaA; + connectedPlayers[index].currentArea = areaA; } // Give an intro: Display the Silverkin Industries logo and splash text. @@ -96,7 +92,7 @@ int main() perror("Server listening is \033[33;40mRED.\033[0m Aborting launch.\n"); exit(0); } - else + else { slowPrint(" Server listening is \033[32;40mGREEN.\033[0m\n", 5000); } @@ -130,7 +126,7 @@ int main() activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); // Check if select() worked: - if ((activityCheck < 0) && (errno != EINTR)) + if ((activityCheck < 0) && (errno != EINTR)) { perror("Error in select(), retrying.\n"); } @@ -139,7 +135,7 @@ int main() if (FD_ISSET(socketFileDesc, &connectedClients)) { if ((connectionFileDesc = accept(socketFileDesc, - (struct sockaddr *)&clientAddress, (socklen_t*)&length))<0) + (struct sockaddr *)&clientAddress, (socklen_t*)&length))<0) { perror("Failed to accept connection. Aborting.\n"); exit(EXIT_FAILURE); @@ -147,8 +143,8 @@ int main() // Print new connection details: printf("Client connected: Socket file descriptor: #%d, IP address: %s, Port: %d.\n", - connectionFileDesc, inet_ntoa(clientAddress.sin_addr) , ntohs - (clientAddress.sin_port)); + connectionFileDesc, inet_ntoa(clientAddress.sin_addr) , ntohs + (clientAddress.sin_port)); // See if we can put in the client: for (int i = 0; i < maxClients; i++) @@ -174,17 +170,18 @@ int main() //Check if it was for closing, and also read the incoming message explicit_bzero(receiveBuffer, sizeof(receiveBuffer)); readLength = read(socketCheck, receiveBuffer, sizeof(receiveBuffer)); + userInputSanatize(receiveBuffer, 2048); if (readLength == 0) { // Somebody disconnected , get his details and print: getpeername(socketCheck, (struct sockaddr*)&clientAddress, (socklen_t*)&length); printf("Client disconnected: IP Address: %s, Port: %d.\n", - inet_ntoa(clientAddress.sin_addr) , ntohs(clientAddress.sin_port)); + inet_ntoa(clientAddress.sin_addr) , ntohs(clientAddress.sin_port)); // Close the socket and mark as 0 in list for reuse: close(socketCheck); - clientSockets[i] = 0; - } + clientSockets[i] = 0; + } // Name change command: Move logic to a command interpreter later: else if (receiveBuffer[0] == '/') { @@ -207,12 +204,33 @@ int main() break; } } - strncpy(connectedPlayers[i].playerName, newName, 32); + if(newName[0] != '\0') + { + strncpy(connectedPlayers[i].playerName, newName, 32); + } } - else if(strncmp(receiveBuffer, "/MOVE", 5) == 0) + else if(strncmp(receiveBuffer, "/LOOK", 5) == 0) + { + strcat(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaDescription); + strcat(messageBuffer.messageContent, "\nYou see:"); + for(int index = 0; index < 16; index++) + { + if(connectedPlayers[i].currentArea->areaExits[index] != NULL) + { + strcat(messageBuffer.messageContent, "\n - "); + strcat(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaExits[index]->pathName); + } + } + write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); + write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); + bzero(messageBuffer.senderName, sizeof(messageBuffer.senderName)); + bzero(messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); + } + else if(strncmp(receiveBuffer, "/MOVE", 5) == 0) { char requestedPath[32]; strncpy(requestedPath, &receiveBuffer[6], 32); + userInputSanatize(requestedPath, 32); // Remove newlines: for (int index = 0; index < 32; index++) { @@ -221,26 +239,46 @@ int main() requestedPath[index] = '\0'; } } - movePlayerToArea(&connectedPlayers[i], requestedPath); + requestedPath[31] = '\0'; + if(movePlayerToArea(&connectedPlayers[i], requestedPath) == 0) + { + strcpy(messageBuffer.senderName, "\0"); + strcpy(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaDescription); + write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); + write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); + } + else + { + strcpy(messageBuffer.senderName, ""); + strcpy(messageBuffer.messageContent, "You can't go somewhere that doesn't exist!"); + write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); + write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); + bzero(messageBuffer.senderName, sizeof(messageBuffer.senderName)); + bzero(messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); + } } } // Echo back the message that came in: + else if (receiveBuffer[0] == '\n') + { + continue; + } else { printf("%d/%s/%s: %s", clientSockets[i], connectedPlayers[i].currentArea->areaName, connectedPlayers[i].playerName, receiveBuffer); fflush(stdout); - strcpy(sendBuffer.senderName, connectedPlayers[i].playerName); - strcpy(sendBuffer.messageContent, receiveBuffer); + strcpy(messageBuffer.senderName, connectedPlayers[i].playerName); + strcpy(messageBuffer.messageContent, receiveBuffer); for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) { if(clientSockets[sendIndex] != STDIN_FILENO && (connectedPlayers[i].currentArea == connectedPlayers[sendIndex].currentArea)) { - write(clientSockets[sendIndex], sendBuffer.senderName, sizeof(sendBuffer.senderName)); - write(clientSockets[sendIndex], sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); + write(clientSockets[sendIndex], messageBuffer.senderName, sizeof(messageBuffer.senderName)); + write(clientSockets[sendIndex], messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); } } - bzero(sendBuffer.senderName, sizeof(sendBuffer.senderName)); - bzero(sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); + bzero(messageBuffer.senderName, sizeof(messageBuffer.senderName)); + bzero(messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); } } } diff --git a/src/misc/inputhandling.c b/src/misc/inputhandling.c index 0f4824b..a90315a 100644 --- a/src/misc/inputhandling.c +++ b/src/misc/inputhandling.c @@ -13,4 +13,6 @@ void userInputSanatize(char * inputString, int length) break; } } + inputString[length - 1] = '\0'; } + diff --git a/src/misc/playerdata.c b/src/misc/playerdata.c index b8279ad..e2f5d88 100644 --- a/src/misc/playerdata.c +++ b/src/misc/playerdata.c @@ -1,19 +1,74 @@ // playerdata.c: Contains functions definitions for working with player data. // Barry Kane, 2021 +#include #include +#include #include "playerdata.h" -// Move a player to a different area given a path in the area. +// Move a player to a different area given a path in the area: int movePlayerToArea(playerInfo * player, char * requestedPath) { - for (int index = 0; index < 32; index++) - { - if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) + for (int index = 0; index < 16; index++) + { + if(player->currentArea->areaExits[index] != NULL) { - player->currentArea = player->currentArea->areaExits[index]->areaToJoin; - return 0; - } + if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) + { + printf("%s: %s\n", player->playerName, player->currentArea->areaExits[index]->pathName); + player->currentArea = player->currentArea->areaExits[index]->areaToJoin; + return 0; + } + } } return 1; } +// Create an area given a name and description: +playerArea * createArea(char * nameString, char * descriptionString) +{ + playerArea * createdArea = calloc(1, sizeof(playerArea)); + strncpy(createdArea->areaName, nameString, 32); + strncpy(createdArea->areaDescription, descriptionString, 256); + for(int index = 0; index < 16; index++) + { + createdArea->areaExits[index] = NULL; + } + return createdArea; +} + +// Create a path between two areas given two areas and two strings: +int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription) +{ + int fromAreaSlot, toAreaSlot; + for(fromAreaSlot = 0; fromAreaSlot < 16; fromAreaSlot++) + { + if(fromArea->areaExits[fromAreaSlot] == NULL) + { + break; + } + if((fromArea->areaExits[fromAreaSlot] != NULL) && (fromAreaSlot == 15)) + { + return 1; + } + } + for(toAreaSlot = 0; toAreaSlot < 32; toAreaSlot++) + { + if(toArea->areaExits[toAreaSlot] == 0) + { + break; + } + if((toArea->areaExits[toAreaSlot] != 0) && (toAreaSlot == 31)) + { + return 2; + } + } + playerPath * fromPath = calloc(1, sizeof(playerPath)); + playerPath * toPath = calloc(1, sizeof(playerPath)); + fromArea->areaExits[fromAreaSlot] = fromPath; + toArea->areaExits[toAreaSlot] = toPath; + strncpy(fromArea->areaExits[fromAreaSlot]->pathName, fromDescription, 32); + strncpy(toArea->areaExits[toAreaSlot]->pathName, toDescription, 32); + fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea; + toArea->areaExits[toAreaSlot]->areaToJoin = fromArea; +} + diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h index b986f53..1c13f2f 100644 --- a/src/misc/playerdata.h +++ b/src/misc/playerdata.h @@ -1,11 +1,14 @@ // playerdata.h: Header file containing data structures for player data and function // definitions for interacting with said data. // Barry Kane, 2021. +#ifndef PLAYERDATA_H +#define PLAYERDATA_H +#include typedef struct userMessage { char senderName[32]; - char messageContent[1024]; + char messageContent[2048]; } userMessage; @@ -21,7 +24,8 @@ struct playerPath struct playerArea { char areaName[32]; - playerPath * areaExits[32]; + char areaDescription[256]; + playerPath * areaExits[16]; }; typedef struct playerInfo @@ -30,5 +34,13 @@ typedef struct playerInfo playerArea * currentArea; } playerInfo; -// Move a player to a different area given a path in the area. +// Move a player to a different area given a path in the area: int movePlayerToArea(playerInfo * player, char * requestedPath); + +// Create an area given a name and description: +playerArea * createArea(char * nameString, char * descriptionString); + +// Create a path between two areas given two areas and a string: +int createPath(playerArea * fromArea, playerArea * toArea, char * pathFromString, char * pathToString); + +#endif From 6c93805d6f3bf5f660562f0ec4c2b267ca124a76 Mon Sep 17 00:00:00 2001 From: Barry Date: Sun, 5 Dec 2021 23:33:53 +0000 Subject: [PATCH 10/53] Added initial implementation of doubly-linked lists - Added lists.c - Added lists.h - Changed initialisation of rooms to add a third room and to add the rooms to a list. - Added datastructures for area and path nodes for doubly-linked lists. --- src/SilverMUD.org | 16 +++--- src/SilverMUDServer.c | 14 ++--- src/misc/lists.c | 130 ++++++++++++++++++++++++++++++++++++++++++ src/misc/lists.h | 51 +++++++++++++++++ 4 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 src/misc/lists.c create mode 100644 src/misc/lists.h diff --git a/src/SilverMUD.org b/src/SilverMUD.org index 4d31210..a68d25d 100644 --- a/src/SilverMUD.org +++ b/src/SilverMUD.org @@ -1,8 +1,8 @@ -* Game Design -** The gameplay: -- Each player may travel to areas connected to the current one, depending on flags. -- Players may perform actions, success depending on their stats. -- Players may create characters. -- A GM may create content using GNU Guile. -* Implementation Plans -* TODOs +* SilverMUD: An extensible terminal-top role playing game engine +SilverMUD is a tool for creating engaging and communal stories, all over the +world through the internet. It's designed to give a game master the same power +to improvise that they have at the table, through simple programming and +easy-to-understand structures. +** Player's Guide +** Gamemaster's Guide +** Developer's Guide diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index f6b1dd5..9832797 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -12,6 +12,7 @@ #include #include #include +#include "misc/lists.h" #include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputhandling.h" @@ -27,22 +28,21 @@ int main() userMessage messageBuffer; char receiveBuffer[2048]; fd_set connectedClients; - playerArea * areaA, * areaB, * areaC; playerInfo connectedPlayers[64]; struct sockaddr_in serverAddress, clientAddress; // Initialize areas: - areaA = createArea("Spawn - North", "A large area, mostly empty, as if the designer hadn't bothered to put anything in it, just yet."); - areaB = createArea("Spawn - South", "A strange, white void. You feel rather uncomfortable."); - areaC = 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(areaA, areaB, "To South Spawn", "To North Spawn"); - createPath(areaC, areaB, "Back to South Spawn", "Path to Enlightenment."); + 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 < maxClients; index++) { strcpy(connectedPlayers[index].playerName, "UNNAMED"); - connectedPlayers[index].currentArea = areaA; + connectedPlayers[index].currentArea = getAreaFromList(areas, 0); } // Give an intro: Display the Silverkin Industries logo and splash text. diff --git a/src/misc/lists.c b/src/misc/lists.c new file mode 100644 index 0000000..c85ab0b --- /dev/null +++ b/src/misc/lists.c @@ -0,0 +1,130 @@ +// Implementation of lists library for SilverMUD. +// Barry Kane, 2021 +#include "lists.h" +#include "playerdata.h" + +areaNode * createAreaList(playerArea * initialArea) +{ + areaNode * newAreaList = malloc(sizeof(areaNode)); + newAreaList->data = initialArea; + newAreaList->next = NULL; + newAreaList->prev = NULL; + return newAreaList; +} + + +pathNode * createPathList(playerPath * initialPath) +{ + pathNode * newPathList = malloc(sizeof(pathNode)); + newPathList->data = initialPath; + newPathList->next = NULL; + newPathList->prev = NULL; + return newPathList; +} + +int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) +{ + areaNode * current; + current = toList; + while(current->next != NULL) + { + current = current->next; + } + current->next = malloc(sizeof(areaNode)); + current->next->prev = current; + current->next->data = areaToAdd; + current->next->next = NULL; +} + +int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) +{ + areaNode * current = fromList; + while(current->data != areaToDelete && current->next != NULL) + { + current = current->next; + } + if(current->next == NULL && current->data != areaToDelete) + { + return 0; + } + current->prev->next = current->next; + if(current->next != NULL) + { + current->next->prev = current->prev; + } + free(current); +} + +int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) +{ + pathNode * current; + current = toList; + while(current->next != NULL) + { + current = current->next; + } + current->next = malloc(sizeof(pathNode)); + current->next->prev = current; + current->next->data = pathToAdd; + current->next->next = NULL; +} + +int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) +{ + pathNode * current = fromList; + while(current->data != pathToDelete || current->next != NULL) + { + current = current->next; + } + if(current->next == NULL && current->data != pathToDelete) + { + return 0; + } + current->prev->next = current->next; + if(current->next != NULL) + { + current->next->prev = current->prev; + } + free(current); +} + +areaNode * getAreaNode(areaNode * fromList, int listIndex) +{ + areaNode * current = fromList; + for(int index = 0; index < listIndex; index++) + { + if(current->next != NULL) + { + current = current->next; + } + else + { + return NULL; + } + } + return current; +} + +pathNode * getPathNode(pathNode * fromList, int listIndex) +{ + pathNode * current = fromList; + for(int index = 0; index < listIndex; index++) + { + if(current->next != NULL) + { + current = current->next; + } + else + { + return NULL; + } + } + return current; +} + +playerArea * getAreaFromList(areaNode * fromList, int listIndex) +{ + areaNode * current = getAreaNode(fromList, listIndex); + return current->data; +} + diff --git a/src/misc/lists.h b/src/misc/lists.h new file mode 100644 index 0000000..d667dc7 --- /dev/null +++ b/src/misc/lists.h @@ -0,0 +1,51 @@ +// lists.h: A header file for the lists library for SilverMUD. +// Barry Kane, 2021. +#ifndef LISTS_H +#define LISTS_H +#include "playerdata.h" + +typedef struct areaNode areaNode; +typedef struct pathNode pathNode; + +struct pathNode +{ + playerPath * data; + pathNode * next; + pathNode * prev; +}; + +struct areaNode +{ + playerArea * data; + areaNode * next; + areaNode * prev; +}; + +areaNode * createAreaList(playerArea * initialArea); + +pathNode * createPathList(playerPath * initialPath); + +int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd); + +int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete); + +int addPathNodeToList(pathNode * toList, playerPath * pathToAdd); + +int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete); + +areaNode * getAreaNode(areaNode * fromList, int listIndex); + +pathNode * getPathNode(pathNode * fromList, int listIndex); + +playerArea * getAreaFromList(areaNode * fromList, int listIndex); + +// TO BE IMPLEMENTED: +/* int saveAreaList(areaNode * listToSave); */ + +/* int savePathList(pathNode * listToSave); */ + +/* int loadAreaList(areaNode * listToLoad); */ + +/* int loadPathList(pathNode * listToLoad); */ + +#endif From 235ff8e74ffb1afdbd7585b5226a3efb5dff4b9a Mon Sep 17 00:00:00 2001 From: Barry Date: Sun, 26 Dec 2021 19:07:30 +0000 Subject: [PATCH 11/53] Added basic logging support and command-line options to the client. - Added basic logging support to the client. - Added basic command-line options to the client: -g: Enables a game-log, must have a file-path. -c: Enables a chat-log, must have a file-path. -i: Sets the IP address to connect to. - Removed the C-c handler, appeared to be broken anyways. Consider reimplementation at some point. - Added /EXIT command to allow for leaving the game. - The client now exits gracefully if the server dies. --- src/SilverMUDClient.c | 129 +++++++++++++++++++++++++++++++++--------- src/SilverMUDServer.c | 21 +++++-- 2 files changed, 117 insertions(+), 33 deletions(-) diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 0e1e197..2806db5 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -1,9 +1,9 @@ #include #include +#include #include #include #include -#include #include #include #include @@ -20,17 +20,14 @@ typedef struct threadparameters { int socketDescriptor; + FILE * loggingstream; + bool loggingflag; WINDOW * window; } threadparameters; // A globally availible exit boolean. bool shouldExit = false; -void sigintHandler(int signal) -{ - shouldExit = true; -} - void * messageSender(void * parameters) { // Takes user input in a window, sanatizes it, and sends it to the server: @@ -50,8 +47,14 @@ void * messageSender(void * parameters) { continue; } + if(threadParameters->loggingflag == true) + { + fputs(sendBuffer, threadParameters->loggingstream); + fputs("\n", threadParameters->loggingstream); + fflush(threadParameters->loggingstream); + } write(threadParameters->socketDescriptor, sendBuffer, MAX); - } + } pthread_exit(NULL); } @@ -65,15 +68,26 @@ void * messageReceiver(void * parameters) { read(threadParameters->socketDescriptor, &receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); read(threadParameters->socketDescriptor, &receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); - if(receiveBuffer.senderName[0] == '\0') { + if(receiveBuffer.messageContent[0] == '\0') + { + shouldExit = true; + pthread_exit(NULL); + } slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); } else { + if(threadParameters->loggingflag == true) + { + fputs(receiveBuffer.senderName, threadParameters->loggingstream); + fputs(": ", threadParameters->loggingstream); + fputs(receiveBuffer.messageContent, threadParameters->loggingstream); + fflush(threadParameters->loggingstream); + } slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window); slowPrintNcurses(": ", 8000, threadParameters->window); slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); @@ -91,13 +105,63 @@ int main(int argc, char **argv) struct sockaddr_in servaddr; pthread_t sendingThread; pthread_t receivingThread; - - // Set the SIGINT handler. - signal(SIGINT, sigintHandler); + int port = 5000; + int currentopt = 0; + char chatlogpath[PATH_MAX + 1]; + char gamelogpath[PATH_MAX + 1]; + char ipaddress[32] = "127.0.0.1"; + FILE * chatlog = NULL, * gamelog = NULL; + bool chatlogging = false, gamelogging = false; // Print welcome message: slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.3\n", 5000); - + + // Parse command-line options: + while((currentopt = getopt(argc, argv, "i:c:g:p:")) != -1) + { + switch(currentopt) + { + case 'i': + { + strncpy(ipaddress, optarg, 32); + break; + } + case 'c': + { + strncpy(chatlogpath, optarg, PATH_MAX + 1); + chatlog = fopen(chatlogpath, "a+"); + if(chatlog == NULL) + { + chatlogging = false; + } + else + { + chatlogging = true; + } + break; + } + case 'g': + { + strncpy(gamelogpath, optarg, PATH_MAX + 1); + gamelog = fopen(gamelogpath, "a+"); + if(gamelog == NULL) + { + gamelogging = false; + } + else + { + gamelogging = true; + } + break; + } + case '?': + { + return 1; + break; + } + } + } + // Give me a socket, and make sure it's working: sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) @@ -113,15 +177,8 @@ int main(int argc, char **argv) // Set our IP Address and port. Default to localhost for testing: servaddr.sin_family = AF_INET; - if (argc == 1) - { - servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); - } - else - { - servaddr.sin_addr.s_addr = inet_addr(argv[1]); - } - servaddr.sin_port = htons(PORT); + servaddr.sin_addr.s_addr = inet_addr(ipaddress); + servaddr.sin_port = htons(port); // Connect the server and client sockets, Kronk: if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) @@ -148,9 +205,19 @@ int main(int argc, char **argv) // Make the windows for the structs, and pass the socket descriptor: logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); logArea->socketDescriptor = sockfd; + logArea->loggingflag = chatlogging; + if(chatlog != NULL) + { + logArea->loggingstream = chatlog; + } messageArea->window = newwin(3, COLS, LINES - 3, 0); messageArea->socketDescriptor = sockfd; - + messageArea->loggingflag = gamelogging; + if(gamelog != NULL) + { + messageArea->loggingstream = gamelog; + } + // Set the two windows to scroll: scrollok(logArea->window, true); scrollok(messageArea->window, true); @@ -159,15 +226,11 @@ int main(int argc, char **argv) pthread_create(&sendingThread, NULL, messageSender, messageArea); pthread_create(&receivingThread, NULL, messageReceiver, logArea); - // Wait for SIGINT to change - while(!shouldExit) - { - sleep(250); - } + // Wait for /EXIT: + pthread_join(receivingThread, NULL); // Close the threads: pthread_cancel(sendingThread); - pthread_cancel(receivingThread); // Close the socket: close(sockfd); @@ -175,6 +238,16 @@ int main(int argc, char **argv) // Free the structs: free(logArea); free(messageArea); + + // Close the files: + if(gamelog != NULL) + { + fclose(gamelog); + } + if(chatlog != NULL) + { + fclose(chatlog); + } // Unsetup Ncurses: endwin(); diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 9832797..126d956 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -31,7 +31,7 @@ int main() playerInfo connectedPlayers[64]; struct sockaddr_in serverAddress, clientAddress; - // Initialize areas: + // Initialize 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.")); @@ -98,7 +98,7 @@ int main() } length = sizeof(clientAddress); - // Accept the data packet from client and verification + // Accept the data packet from client and verify it: while (1) { FD_ZERO(&connectedClients); @@ -134,8 +134,7 @@ int main() // If it's the master socket selected, there is a new connection: if (FD_ISSET(socketFileDesc, &connectedClients)) { - if ((connectionFileDesc = accept(socketFileDesc, - (struct sockaddr *)&clientAddress, (socklen_t*)&length))<0) + if ((connectionFileDesc = accept(socketFileDesc, (struct sockaddr *)&clientAddress, (socklen_t*)&length))<0) { perror("Failed to accept connection. Aborting.\n"); exit(EXIT_FAILURE); @@ -181,7 +180,7 @@ int main() // Close the socket and mark as 0 in list for reuse: close(socketCheck); clientSockets[i] = 0; - } + } // Name change command: Move logic to a command interpreter later: else if (receiveBuffer[0] == '/') { @@ -209,6 +208,18 @@ int main() strncpy(connectedPlayers[i].playerName, newName, 32); } } + else if(strncmp(receiveBuffer, "/EXIT", 5) == 0) + { + strcpy(messageBuffer.senderName, "\0"); + strcpy(messageBuffer.messageContent, "\0"); + write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); + write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); + printf("Client disconnected: IP Address: %s, Port: %d.\n", + inet_ntoa(clientAddress.sin_addr) , ntohs(clientAddress.sin_port)); + + close(socketCheck); + clientSockets[i] = 0; + } else if(strncmp(receiveBuffer, "/LOOK", 5) == 0) { strcat(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaDescription); From 5d772df46985213f10cc955ad2db1975f7078e15 Mon Sep 17 00:00:00 2001 From: Barry Date: Sun, 6 Mar 2022 00:36:42 +0000 Subject: [PATCH 12/53] Added initial GnuTLS encryption. - Added inputoutput.c - Added inputoutput.h - inputoutput contains wrapper and helper functions for transmitting messages over GnuTLS. - Moved the userMessage struct definition to inputoutput. - Reworked client and server to use GnuTLS. - Removed all commands from server in preperation for upcoming command and message queues. - Names and areas are no longer considered for messaging. - Changed Makefile to link GnuTLS. --- Makefile | 4 +- src/SilverMUDClient.c | 67 ++++++---- src/SilverMUDServer.c | 276 ++++++++++++++++------------------------- src/misc/inputoutput.c | 33 +++++ src/misc/inputoutput.h | 21 ++++ src/misc/playerdata.h | 9 +- src/tests/list-test.c | 24 ++++ 7 files changed, 234 insertions(+), 200 deletions(-) create mode 100644 src/misc/inputoutput.c create mode 100644 src/misc/inputoutput.h create mode 100644 src/tests/list-test.c diff --git a/Makefile b/Makefile index 3d4b140..10dea79 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ clientobj = $(clientsrc:.c=.o) serversrc = $(wildcard src/misc/*.c) \ src/SilverMUDServer.c serverobj = $(serversrc:.c=.o) -CLIENTLDFLAGS= -lpthread -lncurses -SERVERLDFLAGS= -lncurses +CLIENTLDFLAGS= -lpthread -lncurses -lgnutls +SERVERLDFLAGS= -lncurses -lgnutls SilverMUDClient: $(clientobj) gcc -s -O3 -o $@ $^ $(CLIENTLDFLAGS) diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 2806db5..b339917 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -9,17 +9,19 @@ #include #include #include +#include #include "misc/playerdata.h" #include "misc/texteffects.h" +#include "misc/inputoutput.h" #include "misc/inputhandling.h" -#define MAX 2048 #define PORT 5000 #define SA struct sockaddr +static int MAX = 2048; // A struct for passing arguments to our threads containing a file descriptor and a window pointer: typedef struct threadparameters { - int socketDescriptor; + gnutls_session_t tlssession; FILE * loggingstream; bool loggingflag; WINDOW * window; @@ -32,28 +34,28 @@ void * messageSender(void * parameters) { // Takes user input in a window, sanatizes it, and sends it to the server: struct threadparameters *threadParameters = parameters; - char sendBuffer[MAX]; + userMessage sendBuffer; while (!shouldExit) { - bzero(sendBuffer, MAX); wprintw(threadParameters->window, "\n\n\nCOMM-LINK> "); - if(wgetnstr(threadParameters->window, sendBuffer, MAX) == ERR) + if(wgetnstr(threadParameters->window, sendBuffer.messageContent, MAX) == ERR) { // Quit if there's any funny business with getting input: pthread_exit(NULL); } - if(sendBuffer[0] == '\n') + if(sendBuffer.messageContent[0] == '\n') { continue; } if(threadParameters->loggingflag == true) { - fputs(sendBuffer, threadParameters->loggingstream); + fputs(sendBuffer.messageContent, threadParameters->loggingstream); fputs("\n", threadParameters->loggingstream); fflush(threadParameters->loggingstream); } - write(threadParameters->socketDescriptor, sendBuffer, MAX); + wprintw(threadParameters->window, sendBuffer.messageContent); + messageSend(threadParameters->tlssession, &sendBuffer); } pthread_exit(NULL); } @@ -66,15 +68,14 @@ void * messageReceiver(void * parameters) userMessage receiveBuffer; while (!shouldExit) { - read(threadParameters->socketDescriptor, &receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); - read(threadParameters->socketDescriptor, &receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); + messageReceive(threadParameters->tlssession, &receiveBuffer); if(receiveBuffer.senderName[0] == '\0') { - if(receiveBuffer.messageContent[0] == '\0') - { - shouldExit = true; - pthread_exit(NULL); - } + //if(receiveBuffer.messageContent[0] == '\0') + //{ + // shouldExit = true; + // pthread_exit(NULL); + //} slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); @@ -92,9 +93,6 @@ void * messageReceiver(void * parameters) slowPrintNcurses(": ", 8000, threadParameters->window); slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); } - - bzero(receiveBuffer.senderName, sizeof(receiveBuffer.senderName)); - bzero(receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); } pthread_exit(NULL); } @@ -191,8 +189,31 @@ int main(int argc, char **argv) slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); } usleep(100000); - - // Setup Ncurses: + + /* TODO: Negotiate TLS + Need to pull in GNU TLS, and do the same on the server-side. */ + // Setup a GnuTLS session and initialize it: + gnutls_session_t tlssession = NULL; + if(gnutls_init(&tlssession, GNUTLS_CLIENT) < 0) + { + // Failure Case + exit(EXIT_FAILURE); + } + gnutls_anon_client_credentials_t clientkey = NULL; + gnutls_anon_allocate_client_credentials(&clientkey); + gnutls_credentials_set(tlssession, GNUTLS_CRD_ANON, &clientkey); + /* Bind the open socket to the TLS session. */ + gnutls_transport_set_int(tlssession, sockfd); + gnutls_priority_set_direct(tlssession, "PERFORMANCE:+ANON-ECDH:+ANON-DH", NULL); + + /* Set default timeout for the handshake. */ + gnutls_handshake_set_timeout(tlssession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + int r = -1; + do { + r = gnutls_handshake(tlssession); + } while (r < 0 && gnutls_error_is_fatal(r) == 0); + + // Setup Ncurses: initscr(); // Create two pointers to structs to pass arguments to the threads: @@ -204,14 +225,14 @@ int main(int argc, char **argv) // Make the windows for the structs, and pass the socket descriptor: logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); - logArea->socketDescriptor = sockfd; + logArea->tlssession = tlssession; logArea->loggingflag = chatlogging; if(chatlog != NULL) { logArea->loggingstream = chatlog; } messageArea->window = newwin(3, COLS, LINES - 3, 0); - messageArea->socketDescriptor = sockfd; + messageArea->tlssession = tlssession; messageArea->loggingflag = gamelogging; if(gamelog != NULL) { @@ -244,7 +265,7 @@ int main(int argc, char **argv) { fclose(gamelog); } - if(chatlog != NULL) + if(chatlog != NULL) { fclose(chatlog); } diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 126d956..1336698 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -12,25 +12,29 @@ #include #include #include +#include #include "misc/lists.h" #include "misc/playerdata.h" #include "misc/texteffects.h" +#include "misc/inputoutput.h" #include "misc/inputhandling.h" + const int PORT = 5000; +const int PLAYERCOUNT = 64; typedef struct sockaddr sockaddr; int main() -{ +{ + bool keepRunning = true; int socketFileDesc, connectionFileDesc, length, clientsAmount, - socketCheck, activityCheck, readLength; - int clientSockets[64]; - int maxClients = 64; - userMessage messageBuffer; - char receiveBuffer[2048]; + socketCheck, activityCheck, readLength, returnVal; fd_set connectedClients; - playerInfo connectedPlayers[64]; + int clientSockets[PLAYERCOUNT]; + userMessage sendBuffer, receiveBuffer; + playerInfo connectedPlayers[PLAYERCOUNT]; + char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; - + // Initialize 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.")); @@ -39,7 +43,7 @@ int main() createPath(getAreaFromList(areas, 2), getAreaFromList(areas, 1), "Back to South Spawn", "Path to Enlightenment."); // Initialize playerdata: - for (int index = 0; index < maxClients; index++) + for (int index = 0; index < PLAYERCOUNT; index++) { strcpy(connectedPlayers[index].playerName, "UNNAMED"); connectedPlayers[index].currentArea = getAreaFromList(areas, 0); @@ -50,7 +54,7 @@ int main() slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.3\n", 5000); // Initialize the sockets to 0, so we don't crash. - for (int index = 0; index < maxClients; index++) + for (int index = 0; index < PLAYERCOUNT; index++) { clientSockets[index] = 0; } @@ -59,13 +63,13 @@ int main() socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); if (socketFileDesc == -1) { - perror("Socket creation is \033[33;40mRED.\033[0m Aborting launch.\n"); + perror("\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); exit(0); } else { - slowPrint(" Socket creation is \033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000); } bzero(&serverAddress, sizeof(serverAddress)); @@ -78,37 +82,59 @@ int main() // Binding newly created socket to given IP, and checking it works: if ((bind(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress))) != 0) { - perror("Socket binding is \033[33;40mRED.\033[0m Aborting launch.\n"); + perror("\tSocket Binding is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); exit(0); } + else { - slowPrint(" Socket binding is \033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", 5000); } // Let's start listening: - if ((listen(socketFileDesc, 64)) != 0) + if ((listen(socketFileDesc, PLAYERCOUNT)) != 0) { - perror("Server listening is \033[33;40mRED.\033[0m Aborting launch.\n"); - exit(0); + perror("\tServer Listening is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + exit(EXIT_FAILURE); } else { - slowPrint(" Server listening is \033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tServer Listening is:\t\033[32;40mGREEN.\033[0m\n", 5000); } length = sizeof(clientAddress); - // Accept the data packet from client and verify it: - while (1) + gnutls_session_t tlssessions[PLAYERCOUNT]; + gnutls_anon_server_credentials_t serverkey = NULL; + gnutls_anon_allocate_server_credentials(&serverkey); + gnutls_anon_set_server_known_dh_params(serverkey, GNUTLS_SEC_PARAM_MEDIUM); + + // Initialize all the TLS Sessions to NULL: We use this to check if it's an "empty connection." + for (int index = 0; index < PLAYERCOUNT; index++) + { + tlssessions[index] = NULL; + if (gnutls_init(&tlssessions[index], GNUTLS_SERVER) < 0) + { + perror("\tTLS Sessions Initialization is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + exit(EXIT_FAILURE); + } + gnutls_priority_set_direct(tlssessions[index], "NORMAL:+ANON-ECDH:+ANON-DH", NULL); + gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); + gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + } + slowPrint("\tTLS Sessions Initialization is:\t\033[32;40mGREEN.\033[0m\n", 5000); + + while(keepRunning) { + // Clear the set of file descriptors and add the master socket: FD_ZERO(&connectedClients); FD_SET(socketFileDesc, &connectedClients); clientsAmount = socketFileDesc; - bzero(receiveBuffer, sizeof(receiveBuffer)); - for (int i = 0; i < maxClients; i++) - { + + // Find all sockets that are still working and place them in the set: + for(int index = 0; index < PLAYERCOUNT; index++) + { // Just get the one we're working with to another name: - socketCheck = clientSockets[i]; + socketCheck = clientSockets[index]; // If it's working, bang it into the list: if(socketCheck > 0) @@ -134,165 +160,81 @@ int main() // If it's the master socket selected, there is a new connection: if (FD_ISSET(socketFileDesc, &connectedClients)) { - if ((connectionFileDesc = accept(socketFileDesc, (struct sockaddr *)&clientAddress, (socklen_t*)&length))<0) + if ((connectionFileDesc = accept(socketFileDesc, (struct sockaddr *)&clientAddress, (socklen_t*)&length)) < 0) { perror("Failed to accept connection. Aborting.\n"); exit(EXIT_FAILURE); - } - - // Print new connection details: - printf("Client connected: Socket file descriptor: #%d, IP address: %s, Port: %d.\n", - connectionFileDesc, inet_ntoa(clientAddress.sin_addr) , ntohs - (clientAddress.sin_port)); - + } // See if we can put in the client: - for (int i = 0; i < maxClients; i++) + for (int index = 0; index < PLAYERCOUNT; index++) { // When there is an empty slot, pop it in: - if( clientSockets[i] == 0 ) + if (clientSockets[index] == 0) { - clientSockets[i] = connectionFileDesc; - printf("Adding to list of sockets as %d.\n" , i); + clientSockets[index] = connectionFileDesc; + printf("Adding to list of sockets as %d.\n", index); + gnutls_transport_set_int(tlssessions[index], clientSockets[index]); + do + { + returnVal = gnutls_handshake(tlssessions[index]); + } + while (returnVal < 0 && gnutls_error_is_fatal(returnVal) == 0); + strcpy(sendBuffer.senderName, ""); + strcpy(sendBuffer.messageContent, "Welcome to the server!"); + messageSend(tlssessions[index], &sendBuffer); break; } } } + // Otherwise, it's a client we need to interact with: else { - // Otherwise, it's a client socket to be interacted with: - for (int i = 0; i < maxClients; i++) + for (int index = 0; index < PLAYERCOUNT; index++) { - socketCheck = clientSockets[i]; - - if (FD_ISSET(socketCheck, &connectedClients)) - { - //Check if it was for closing, and also read the incoming message - explicit_bzero(receiveBuffer, sizeof(receiveBuffer)); - readLength = read(socketCheck, receiveBuffer, sizeof(receiveBuffer)); - userInputSanatize(receiveBuffer, 2048); - if (readLength == 0) + socketCheck = clientSockets[index]; + + if(FD_ISSET(socketCheck, &connectedClients)) + { + messageReceive(tlssessions[index], &receiveBuffer); + sprintf(testString, "User %d", index); + strcpy(sendBuffer.senderName, testString); + userInputSanatize(receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); + strcpy(sendBuffer.messageContent, receiveBuffer.messageContent); + for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) { - // Somebody disconnected , get his details and print: - getpeername(socketCheck, (struct sockaddr*)&clientAddress, (socklen_t*)&length); - printf("Client disconnected: IP Address: %s, Port: %d.\n", - inet_ntoa(clientAddress.sin_addr) , ntohs(clientAddress.sin_port)); - - // Close the socket and mark as 0 in list for reuse: - close(socketCheck); - clientSockets[i] = 0; + messageSend(tlssessions[sendIndex], &sendBuffer); } - // Name change command: Move logic to a command interpreter later: - else if (receiveBuffer[0] == '/') - { - if(strncmp(receiveBuffer, "/NAME", 5) == 0) - { - char newName[32]; - strncpy(newName, &receiveBuffer[6], 32); - // Remove newlines: - for (int index = 0; index < 32; index++) - { - if (newName[index] == '\n') - { - newName[index] = '\0'; - } - } - for (int index = 0; index < maxClients; index++) - { - if(strncmp(newName, connectedPlayers[index].playerName, 32) == 0) - { - break; - } - } - if(newName[0] != '\0') - { - strncpy(connectedPlayers[i].playerName, newName, 32); - } - } - else if(strncmp(receiveBuffer, "/EXIT", 5) == 0) - { - strcpy(messageBuffer.senderName, "\0"); - strcpy(messageBuffer.messageContent, "\0"); - write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); - write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - printf("Client disconnected: IP Address: %s, Port: %d.\n", - inet_ntoa(clientAddress.sin_addr) , ntohs(clientAddress.sin_port)); - - close(socketCheck); - clientSockets[i] = 0; - } - else if(strncmp(receiveBuffer, "/LOOK", 5) == 0) - { - strcat(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaDescription); - strcat(messageBuffer.messageContent, "\nYou see:"); - for(int index = 0; index < 16; index++) - { - if(connectedPlayers[i].currentArea->areaExits[index] != NULL) - { - strcat(messageBuffer.messageContent, "\n - "); - strcat(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaExits[index]->pathName); - } - } - write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); - write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - bzero(messageBuffer.senderName, sizeof(messageBuffer.senderName)); - bzero(messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - } - else if(strncmp(receiveBuffer, "/MOVE", 5) == 0) - { - char requestedPath[32]; - strncpy(requestedPath, &receiveBuffer[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(&connectedPlayers[i], requestedPath) == 0) - { - strcpy(messageBuffer.senderName, "\0"); - strcpy(messageBuffer.messageContent, connectedPlayers[i].currentArea->areaDescription); - write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); - write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - } - else - { - strcpy(messageBuffer.senderName, ""); - strcpy(messageBuffer.messageContent, "You can't go somewhere that doesn't exist!"); - write(socketCheck, messageBuffer.senderName, sizeof(messageBuffer.senderName)); - write(socketCheck, messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - bzero(messageBuffer.senderName, sizeof(messageBuffer.senderName)); - bzero(messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - } - } - } - // Echo back the message that came in: - else if (receiveBuffer[0] == '\n') - { - continue; - } - else - { - printf("%d/%s/%s: %s", clientSockets[i], connectedPlayers[i].currentArea->areaName, connectedPlayers[i].playerName, receiveBuffer); - fflush(stdout); - strcpy(messageBuffer.senderName, connectedPlayers[i].playerName); - strcpy(messageBuffer.messageContent, receiveBuffer); - for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) - { - if(clientSockets[sendIndex] != STDIN_FILENO && (connectedPlayers[i].currentArea == connectedPlayers[sendIndex].currentArea)) - { - write(clientSockets[sendIndex], messageBuffer.senderName, sizeof(messageBuffer.senderName)); - write(clientSockets[sendIndex], messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - } - } - bzero(messageBuffer.senderName, sizeof(messageBuffer.senderName)); - bzero(messageBuffer.messageContent, sizeof(messageBuffer.messageContent)); - } - } + } } - } + } } + /* // TODO: Implement the ability to connect clients, and pass messages to the relevant queues. */ + /* // Check if there's a new client by checking the master socket: */ + /* connectionFileDesc = accept(socketFileDesc,(struct sockaddr *)&clientAddress, (socklen_t*)&length); */ + /* gnutls_transport_set_int(tlssessions[0], connectionFileDesc); */ + /* int returnVal = -1; */ + /* do */ + /* { */ + /* returnVal = gnutls_handshake(tlssessions[0]); */ + /* } */ + /* while (returnVal < 0 && gnutls_error_is_fatal(returnVal) == 0); */ + + /* if (returnVal < 0) */ + /* { */ + /* fprintf(stderr, "*** Handshake has failed (%s)\n\n", gnutls_strerror(returnVal)); */ + /* } */ + /* strcpy(sendBuffer.senderName, "Test"); */ + /* strcpy(sendBuffer.messageContent, "GnuTLS, baybee!\n"); */ + + /* messageSend(tlssessions[0], &sendBuffer); */ + /* while(true) */ + /* { */ + /* messageReceive(tlssessions[0], &receiveBuffer); */ + /* userInputSanatize(receiveBuffer.messageContent, 2048); */ + /* strcpy(sendBuffer.messageContent, receiveBuffer.messageContent); */ + /* messageSend(tlssessions[0], &sendBuffer); */ + /* } */ + /* gnutls_bye(tlssessions[0], GNUTLS_SHUT_RDWR); */ + return 0; } + diff --git a/src/misc/inputoutput.c b/src/misc/inputoutput.c new file mode 100644 index 0000000..f29572d --- /dev/null +++ b/src/misc/inputoutput.c @@ -0,0 +1,33 @@ +// inputoutput.c: Implementation of input output library for SilverMUD. +// Barry Kane, 2022. +#include +#include "inputoutput.h" +#include + +// Sends a message to a given TLS session, wraps the calls to gnutls_write: +void messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) +{ + int returnValue = 0; + do + { + returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, sizeof(((userMessage*)0)->senderName)); + } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + do + { + returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, sizeof(((userMessage*)0)->messageContent)); + } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); +} + +// Recieves a message from a given TLS session, wraps the calls to gnutls_read: +void messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) +{ + int returnValue = 0; + do + { + returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, sizeof(((userMessage*)0)->senderName)); + } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + do + { + returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, sizeof(((userMessage*)0)->messageContent)); + } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); +} diff --git a/src/misc/inputoutput.h b/src/misc/inputoutput.h new file mode 100644 index 0000000..71b1ad9 --- /dev/null +++ b/src/misc/inputoutput.h @@ -0,0 +1,21 @@ +// inputoutput.h: Header file contatning function prototypes and datastructures +// for dealing with input and output. +// Barry Kane, 2022. +#ifndef INPUTOUTPUt_H +#define INPUTOUTPUT_H +#include + +// A message datastructure containing a user/character name and the content: +typedef struct userMessage +{ + char senderName[32]; + char messageContent[2048]; +} userMessage; + +// Sends a message to a given TLS session, wraps the calls to gnutls_write: +void messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); + +// Recieves a message from a given TLS session, wraps the calls to gnutls_read: +void messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); + +#endif diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h index 1c13f2f..f507e9f 100644 --- a/src/misc/playerdata.h +++ b/src/misc/playerdata.h @@ -1,17 +1,10 @@ // playerdata.h: Header file containing data structures for player data and function -// definitions for interacting with said data. +// prototypes for interacting with said data. // Barry Kane, 2021. #ifndef PLAYERDATA_H #define PLAYERDATA_H #include -typedef struct userMessage -{ - char senderName[32]; - char messageContent[2048]; - -} userMessage; - typedef struct playerPath playerPath; typedef struct playerArea playerArea; diff --git a/src/tests/list-test.c b/src/tests/list-test.c new file mode 100644 index 0000000..e522514 --- /dev/null +++ b/src/tests/list-test.c @@ -0,0 +1,24 @@ +#include "../misc/lists.h" +#include "../misc/playerdata.h" +#include + +void main() +{ + areaNode * areaList = createAreaList(createArea("Test Area A", "This is Test Area A")); + areaNode * counter = areaList; + addAreaNodeToList(areaList, createArea("Test Area B", "This is Test Area B")); + addAreaNodeToList(areaList, createArea("Test Area C", "This is Test Area C")); + for(int index = 0; index <= 2; index++) + { + printf("%s\n", counter->data->areaName); + counter = counter->next; + } + deleteAreaNodeFromList(areaList, getAreaFromList(areaList, 1)); + addAreaNodeToList(areaList, createArea("Test Area D", "This is Test Area D")); + counter = areaList; + for(int index = 0; index <= 2; index++) + { + printf("%s\n", counter->data->areaName); + counter = counter->next; + } +} From e4b86930374c70a8d95e1c9986ef90a77cf65f4f Mon Sep 17 00:00:00 2001 From: Barry Date: Tue, 15 Mar 2022 14:52:49 +0000 Subject: [PATCH 13/53] Cleaned up client. - Cleaned up the client codebase. - Throughly commented SilverMUDClient.c. - Added a boolean for bolding slowPrintNcurses. - Added a user-configurable delay for text printing. - Other small improvements. --- src/SilverMUDClient.c | 227 +++++++++++++++++++++++------------------ src/SilverMUDServer.c | 35 ------- src/misc/texteffects.c | 10 +- src/misc/texteffects.h | 2 +- 4 files changed, 137 insertions(+), 137 deletions(-) diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index b339917..9fd91f5 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -1,3 +1,6 @@ +// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.3. +// PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. +// Barry Kane, 2021 #include #include #include @@ -14,84 +17,91 @@ #include "misc/texteffects.h" #include "misc/inputoutput.h" #include "misc/inputhandling.h" -#define PORT 5000 -#define SA struct sockaddr static int MAX = 2048; // A struct for passing arguments to our threads containing a file descriptor and a window pointer: typedef struct threadparameters { - gnutls_session_t tlssession; - FILE * loggingstream; - bool loggingflag; + gnutls_session_t tlsSession; + FILE * loggingStream; + bool loggingFlag; WINDOW * window; } threadparameters; -// A globally availible exit boolean. +// Use sockaddr as a type: +typedef struct sockaddr sockaddr; + +// A globally available exit boolean: bool shouldExit = false; void * messageSender(void * parameters) { - // Takes user input in a window, sanatizes it, and sends it to the server: struct threadparameters *threadParameters = parameters; userMessage sendBuffer; - + + // Repeatedly get input from the user, place it in a userMessage, and send it to the server: while (!shouldExit) { + // Print the prompt: wprintw(threadParameters->window, "\n\n\nCOMM-LINK> "); - if(wgetnstr(threadParameters->window, sendBuffer.messageContent, MAX) == ERR) + if (wgetnstr(threadParameters->window, sendBuffer.messageContent, MAX) == ERR) { // Quit if there's any funny business with getting input: pthread_exit(NULL); } - if(sendBuffer.messageContent[0] == '\n') + + // Ignore empty messages: + if (sendBuffer.messageContent[0] == '\n') { continue; } - if(threadParameters->loggingflag == true) + + // Send the message to the log if logging is enabled: + if (threadParameters->loggingFlag == true) { - fputs(sendBuffer.messageContent, threadParameters->loggingstream); - fputs("\n", threadParameters->loggingstream); - fflush(threadParameters->loggingstream); + fputs(sendBuffer.messageContent, threadParameters->loggingStream); + fputs("\n", threadParameters->loggingStream); + fflush(threadParameters->loggingStream); } - wprintw(threadParameters->window, sendBuffer.messageContent); - messageSend(threadParameters->tlssession, &sendBuffer); + + // Send the message off to the server: + messageSend(threadParameters->tlsSession, &sendBuffer); } pthread_exit(NULL); } - void * messageReceiver(void * parameters) { - // Takes messages from the server and prints them to the chat log window: struct threadparameters *threadParameters = parameters; userMessage receiveBuffer; + + // Repeatedly take messages from the server and print them to the chat log window: while (!shouldExit) { - messageReceive(threadParameters->tlssession, &receiveBuffer); - if(receiveBuffer.senderName[0] == '\0') + messageReceive(threadParameters->tlsSession, &receiveBuffer); + if (receiveBuffer.senderName[0] == '\0') { - //if(receiveBuffer.messageContent[0] == '\0') - //{ - // shouldExit = true; - // pthread_exit(NULL); - //} - slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); - slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); - slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window); + if (receiveBuffer.messageContent[0] == '\0') + { + shouldExit = true; + pthread_exit(NULL); + } + slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window, true); + slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window, false); + slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window, true); } else { - if(threadParameters->loggingflag == true) + if (threadParameters->loggingFlag == true) { - fputs(receiveBuffer.senderName, threadParameters->loggingstream); - fputs(": ", threadParameters->loggingstream); - fputs(receiveBuffer.messageContent, threadParameters->loggingstream); - fflush(threadParameters->loggingstream); + fputs(receiveBuffer.senderName, threadParameters->loggingStream); + fputs(": ", threadParameters->loggingStream); + fputs(receiveBuffer.messageContent, threadParameters->loggingStream); + fflush(threadParameters->loggingStream); } - slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window); - slowPrintNcurses(": ", 8000, threadParameters->window); - slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window); + slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window, true); + slowPrintNcurses(": ", 8000, threadParameters->window, true); + slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window, false); } } pthread_exit(NULL); @@ -99,59 +109,70 @@ void * messageReceiver(void * parameters) int main(int argc, char **argv) { - int sockfd; - struct sockaddr_in servaddr; + int socketFileDesc; + struct sockaddr_in serverAddress; pthread_t sendingThread; pthread_t receivingThread; int port = 5000; int currentopt = 0; - char chatlogpath[PATH_MAX + 1]; - char gamelogpath[PATH_MAX + 1]; - char ipaddress[32] = "127.0.0.1"; - FILE * chatlog = NULL, * gamelog = NULL; - bool chatlogging = false, gamelogging = false; - + int characterDelay = 8000; + char chatLogPath[PATH_MAX + 1]; + char gameLogPath[PATH_MAX + 1]; + char ipAddress[32] = "127.0.0.1"; + FILE * chatLog = NULL, * gameLog = NULL; + bool chatLogging = false, gameLogging = false; + // Print welcome message: slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.3\n", 5000); // Parse command-line options: - while((currentopt = getopt(argc, argv, "i:c:g:p:")) != -1) + while ((currentopt = getopt(argc, argv, "i:c:g:p:d:")) != -1) { - switch(currentopt) + switch (currentopt) { case 'i': { - strncpy(ipaddress, optarg, 32); + strncpy(ipAddress, optarg, 32); break; } case 'c': { - strncpy(chatlogpath, optarg, PATH_MAX + 1); - chatlog = fopen(chatlogpath, "a+"); - if(chatlog == NULL) + strncpy(chatLogPath, optarg, PATH_MAX + 1); + chatLog = fopen(chatLogPath, "a+"); + if (chatLog == NULL) { - chatlogging = false; + chatLogging = false; } else { - chatlogging = true; + chatLogging = true; } break; } case 'g': { - strncpy(gamelogpath, optarg, PATH_MAX + 1); - gamelog = fopen(gamelogpath, "a+"); - if(gamelog == NULL) + strncpy(gameLogPath, optarg, PATH_MAX + 1); + gameLog = fopen(gameLogPath, "a+"); + if (gameLog == NULL) { - gamelogging = false; + gameLogging = false; } else { - gamelogging = true; + gameLogging = true; } break; } + case 'p': + { + port = atoi(optarg); + break; + } + case 'd': + { + characterDelay = atoi(optarg); + break; + } case '?': { return 1; @@ -161,61 +182,66 @@ int main(int argc, char **argv) } // Give me a socket, and make sure it's working: - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd == -1) + socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); + if (socketFileDesc == -1) { printf("Socket creation failed.\n"); - exit(0); + exit(EXIT_FAILURE); } else { - slowPrint("Socket successfully created.\n", 8000); + slowPrint("Socket successfully created.\n", characterDelay); } - bzero(&servaddr, sizeof(servaddr)); + bzero(&serverAddress, sizeof(serverAddress)); // Set our IP Address and port. Default to localhost for testing: - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr = inet_addr(ipaddress); - servaddr.sin_port = htons(port); + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = inet_addr(ipAddress); + serverAddress.sin_port = htons(port); // Connect the server and client sockets, Kronk: - if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) + if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) { - slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", 8000); + slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay); exit(0); } else { - slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); + slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", characterDelay); } usleep(100000); - /* TODO: Negotiate TLS - Need to pull in GNU TLS, and do the same on the server-side. */ // Setup a GnuTLS session and initialize it: - gnutls_session_t tlssession = NULL; - if(gnutls_init(&tlssession, GNUTLS_CLIENT) < 0) + gnutls_session_t tlsSession = NULL; + if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0) { // Failure Case exit(EXIT_FAILURE); } + + // Setup the private credentials for our GnuTLS session: gnutls_anon_client_credentials_t clientkey = NULL; gnutls_anon_allocate_client_credentials(&clientkey); - gnutls_credentials_set(tlssession, GNUTLS_CRD_ANON, &clientkey); - /* Bind the open socket to the TLS session. */ - gnutls_transport_set_int(tlssession, sockfd); - gnutls_priority_set_direct(tlssession, "PERFORMANCE:+ANON-ECDH:+ANON-DH", NULL); + gnutls_credentials_set(tlsSession, GNUTLS_CRD_ANON, &clientkey); - /* Set default timeout for the handshake. */ - gnutls_handshake_set_timeout(tlssession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - int r = -1; - do { - r = gnutls_handshake(tlssession); - } while (r < 0 && gnutls_error_is_fatal(r) == 0); + // Bind the open socket to the TLS session: + gnutls_transport_set_int(tlsSession, socketFileDesc); + gnutls_priority_set_direct(tlsSession, "PERFORMANCE:+ANON-ECDH:+ANON-DH", NULL); + + // Use the default for the GnuTLS handshake timeout: + gnutls_handshake_set_timeout(tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + // Repeatedly attempt to handshake unless we encounter a fatal error: + int returnValue = -1; + do + { + returnValue = gnutls_handshake(tlsSession); + } + while (returnValue < 0 && gnutls_error_is_fatal(returnValue) == 0); // Setup Ncurses: initscr(); - + // Create two pointers to structs to pass arguments to the threads: threadparameters * logArea; threadparameters * messageArea; @@ -225,18 +251,18 @@ int main(int argc, char **argv) // Make the windows for the structs, and pass the socket descriptor: logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); - logArea->tlssession = tlssession; - logArea->loggingflag = chatlogging; - if(chatlog != NULL) + logArea->tlsSession = tlsSession; + logArea->loggingFlag = chatLogging; + if (chatLog != NULL) { - logArea->loggingstream = chatlog; + logArea->loggingStream = chatLog; } messageArea->window = newwin(3, COLS, LINES - 3, 0); - messageArea->tlssession = tlssession; - messageArea->loggingflag = gamelogging; - if(gamelog != NULL) + messageArea->tlsSession = tlsSession; + messageArea->loggingFlag = gameLogging; + if (gameLog != NULL) { - messageArea->loggingstream = gamelog; + messageArea->loggingStream = gameLog; } // Set the two windows to scroll: @@ -253,26 +279,27 @@ int main(int argc, char **argv) // Close the threads: pthread_cancel(sendingThread); - // Close the socket: - close(sockfd); + // Close the session and socket: + gnutls_bye(tlsSession, GNUTLS_SHUT_RDWR); + close(socketFileDesc); // Free the structs: free(logArea); free(messageArea); - // Close the files: - if(gamelog != NULL) + // Close the log files: + if (gameLog != NULL) { - fclose(gamelog); + fclose(gameLog); } - if(chatlog != NULL) + if (chatLog != NULL) { - fclose(chatlog); + fclose(chatLog); } // Unsetup Ncurses: endwin(); - // Say Goodbye: - slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", 8000); + // Say goodbye: + slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", characterDelay); } diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 1336698..5725eb7 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -35,18 +35,10 @@ int main() char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; - // Initialize 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++) { strcpy(connectedPlayers[index].playerName, "UNNAMED"); - connectedPlayers[index].currentArea = getAreaFromList(areas, 0); } // Give an intro: Display the Silverkin Industries logo and splash text. @@ -208,33 +200,6 @@ int main() } } } - /* // TODO: Implement the ability to connect clients, and pass messages to the relevant queues. */ - /* // Check if there's a new client by checking the master socket: */ - /* connectionFileDesc = accept(socketFileDesc,(struct sockaddr *)&clientAddress, (socklen_t*)&length); */ - /* gnutls_transport_set_int(tlssessions[0], connectionFileDesc); */ - /* int returnVal = -1; */ - /* do */ - /* { */ - /* returnVal = gnutls_handshake(tlssessions[0]); */ - /* } */ - /* while (returnVal < 0 && gnutls_error_is_fatal(returnVal) == 0); */ - - /* if (returnVal < 0) */ - /* { */ - /* fprintf(stderr, "*** Handshake has failed (%s)\n\n", gnutls_strerror(returnVal)); */ - /* } */ - /* strcpy(sendBuffer.senderName, "Test"); */ - /* strcpy(sendBuffer.messageContent, "GnuTLS, baybee!\n"); */ - - /* messageSend(tlssessions[0], &sendBuffer); */ - /* while(true) */ - /* { */ - /* messageReceive(tlssessions[0], &receiveBuffer); */ - /* userInputSanatize(receiveBuffer.messageContent, 2048); */ - /* strcpy(sendBuffer.messageContent, receiveBuffer.messageContent); */ - /* messageSend(tlssessions[0], &sendBuffer); */ - /* } */ - /* gnutls_bye(tlssessions[0], GNUTLS_SHUT_RDWR); */ return 0; } diff --git a/src/misc/texteffects.c b/src/misc/texteffects.c index 875b3cc..5920be3 100644 --- a/src/misc/texteffects.c +++ b/src/misc/texteffects.c @@ -17,9 +17,13 @@ void slowPrint(char * stringToPrint, int delay) } } -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window) +void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) { int characterIndex = 0; + if(bolded) + { + wattron(window, A_BOLD); + } while(stringToPrint[characterIndex] != '\0') { waddch(window, stringToPrint[characterIndex]); @@ -28,5 +32,9 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window) usleep(delay); characterIndex++; } + if(bolded) + { + wattroff(window, A_BOLD); + } wrefresh(window); } diff --git a/src/misc/texteffects.h b/src/misc/texteffects.h index d75e92c..38e5177 100644 --- a/src/misc/texteffects.h +++ b/src/misc/texteffects.h @@ -9,7 +9,7 @@ void slowPrint(char * stringToPrint, int delay); // The same, altered to work with Ncurses. -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window); +void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); // A string containing an ASCII art version of the Silverkin Industries logo. char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; From 4ddb80b8b26f13b240a790d62e9047dfd9cd15f0 Mon Sep 17 00:00:00 2001 From: Barry Date: Thu, 7 Apr 2022 01:38:36 +0100 Subject: [PATCH 14/53] Basic message queuing implemented - Messages are now queued on reception by the server. - Message queue datastructures are now added. --- Makefile | 2 +- src/SilverMUD.org | 4 +- src/SilverMUDClient.c | 3 +- src/SilverMUDServer.c | 56 ++++++-- src/misc/constants.h | 9 ++ src/misc/inputoutput.c | 263 ++++++++++++++++++++++++++++++++++- src/misc/inputoutput.h | 95 ++++++++++++- src/misc/playerdata.h | 5 +- src/tests/outputQueue-test.c | 35 +++++ 9 files changed, 438 insertions(+), 34 deletions(-) create mode 100644 src/misc/constants.h create mode 100644 src/tests/outputQueue-test.c diff --git a/Makefile b/Makefile index 10dea79..a945a26 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ serversrc = $(wildcard src/misc/*.c) \ src/SilverMUDServer.c serverobj = $(serversrc:.c=.o) CLIENTLDFLAGS= -lpthread -lncurses -lgnutls -SERVERLDFLAGS= -lncurses -lgnutls +SERVERLDFLAGS= -lpthread -lncurses -lgnutls SilverMUDClient: $(clientobj) gcc -s -O3 -o $@ $^ $(CLIENTLDFLAGS) diff --git a/src/SilverMUD.org b/src/SilverMUD.org index a68d25d..30aa5c8 100644 --- a/src/SilverMUD.org +++ b/src/SilverMUD.org @@ -1,6 +1,6 @@ -* SilverMUD: An extensible terminal-top role playing game engine +* SilverMUD: The Hackable Terminal-Top Roleplaying Game. SilverMUD is a tool for creating engaging and communal stories, all over the -world through the internet. It's designed to give a game master the same power +world through the internet. It's designed to give a gamemaster the same power to improvise that they have at the table, through simple programming and easy-to-understand structures. ** Player's Guide diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 9fd91f5..409f765 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -13,11 +13,10 @@ #include #include #include +#include "misc/constants.h" #include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputoutput.h" -#include "misc/inputhandling.h" -static int MAX = 2048; // A struct for passing arguments to our threads containing a file descriptor and a window pointer: typedef struct threadparameters diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 5725eb7..6061482 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -14,31 +14,32 @@ #include #include #include "misc/lists.h" +#include "misc/constants.h" #include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputoutput.h" -#include "misc/inputhandling.h" -const int PORT = 5000; -const int PLAYERCOUNT = 64; typedef struct sockaddr sockaddr; int main() { bool keepRunning = true; int socketFileDesc, connectionFileDesc, length, clientsAmount, - socketCheck, activityCheck, readLength, returnVal; + socketCheck, activityCheck, returnVal; fd_set connectedClients; int clientSockets[PLAYERCOUNT]; userMessage sendBuffer, receiveBuffer; playerInfo connectedPlayers[PLAYERCOUNT]; char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; + inputMessageQueue * inputQueue = createInputMessageQueue(); + outputMessageQueue * outputQueue = createOutputMessageQueue(); // Initialize playerdata: for (int index = 0; index < PLAYERCOUNT; index++) { - strcpy(connectedPlayers[index].playerName, "UNNAMED"); + sprintf(testString, "UNNAMED %d", index); + strcpy(connectedPlayers[index].playerName, testString); } // Give an intro: Display the Silverkin Industries logo and splash text. @@ -187,18 +188,47 @@ int main() if(FD_ISSET(socketCheck, &connectedClients)) { - messageReceive(tlssessions[index], &receiveBuffer); - sprintf(testString, "User %d", index); - strcpy(sendBuffer.senderName, testString); - userInputSanatize(receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); - strcpy(sendBuffer.messageContent, receiveBuffer.messageContent); - for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) + if(messageReceive(tlssessions[index], &receiveBuffer) == -10) { - messageSend(tlssessions[sendIndex], &sendBuffer); + gnutls_bye(tlssessions[index], GNUTLS_SHUT_RDWR); + gnutls_deinit(tlssessions[index]); + shutdown(clientSockets[index], 2); + close(clientSockets[index]); + clientSockets[index] = 0; + tlssessions[index] = NULL; + gnutls_init(&tlssessions[index], GNUTLS_SERVER); + gnutls_priority_set_direct(tlssessions[index], "NORMAL:+ANON-ECDH:+ANON-DH", NULL); + gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); + gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + } + else + { + queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); } } } - } + } + // 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) + { + outputMessage * message = peekOutputMessage(outputQueue); + for (int index = 0; index < PLAYERCOUNT; index++) + { + messageSend(tlssessions[index], message->content); + } + dequeueOutputMessage(outputQueue); + } } return 0; } diff --git a/src/misc/constants.h b/src/misc/constants.h new file mode 100644 index 0000000..bb598d8 --- /dev/null +++ b/src/misc/constants.h @@ -0,0 +1,9 @@ +// Constants.h: Contains configurable constants for SilverMUD. +// Barry Kane, 2022. +#ifndef CONSTANTS_H +#define CONSTANTS_H +#define PORT 5000 +#define MAX 2048 +#define PLAYERCOUNT 64 +#define MAXQUEUELENGTH 2048 +#endif diff --git a/src/misc/inputoutput.c b/src/misc/inputoutput.c index f29572d..0bd1fc6 100644 --- a/src/misc/inputoutput.c +++ b/src/misc/inputoutput.c @@ -1,33 +1,282 @@ -// inputoutput.c: Implementation of input output library for SilverMUD. +// inputoutput.c: Implementation of input/output library for SilverMUD. // Barry Kane, 2022. +#include #include +#include +#include +#include +#include "constants.h" +#include "playerdata.h" #include "inputoutput.h" #include // Sends a message to a given TLS session, wraps the calls to gnutls_write: -void messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) +int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) { int returnValue = 0; do { - returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, sizeof(((userMessage*)0)->senderName)); + returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, + sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); do { - returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, sizeof(((userMessage*)0)->messageContent)); + returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, + sizeof(((userMessage*)0)->messageContent)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + return returnValue; } // Recieves a message from a given TLS session, wraps the calls to gnutls_read: -void messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) +int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) { int returnValue = 0; do { - returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, sizeof(((userMessage*)0)->senderName)); + returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, + sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); do { - returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, sizeof(((userMessage*)0)->messageContent)); + returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, + sizeof(((userMessage*)0)->messageContent)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + return returnValue; +} + +outputMessageQueue * createOutputMessageQueue(void) +{ + outputMessageQueue * newQueue = malloc(sizeof(outputMessageQueue)); + newQueue->front = NULL; + newQueue->back = NULL; + newQueue->currentLength = 0; + newQueue->lock = false; + return newQueue; +} + +int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) +{ + // Copy the message into a new output message: + outputMessage * outputMessage = malloc(sizeof(outputMessage)); + + // Allocate the internal userMessage to store the message: + outputMessage->content = malloc(sizeof(userMessage)); + + // Copy the userMessage to the internal userMessage: + strncpy(outputMessage->content->senderName, messageToQueue.senderName, 32); + strncpy(outputMessage->content->messageContent, messageToQueue.messageContent, MAX); + + // We have no targets, NULL sends to all players in an area: + outputMessage->targets[0] = NULL; + + // 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 = outputMessage; + queue->back = outputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + else + { + queue->back->next = outputMessage; + queue->back = outputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + } +} + +int dequeueOutputMessage(outputMessageQueue * queue) +{ + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check the list isn't empty: + if(queue->front == NULL) + { + queue->lock = false; + return -1; + } + + // If there is only one item in the queue: + else if(queue->front == queue->back) + { + free(queue->front->content); + free(queue->front); + queue->front = NULL; + queue->back = NULL; + queue->currentLength--; + queue->lock = false; + return 0; + } + + // Remove the front item: + else + { + outputMessage * messageToDelete = queue->front; + queue->front = queue->front->next; + free(messageToDelete->content); + free(messageToDelete); + queue->currentLength--; + queue->lock = false; + return 0; + } +} + +inputMessageQueue * createInputMessageQueue(void) +{ + inputMessageQueue * newQueue = malloc(sizeof(inputMessageQueue)); + newQueue->front = NULL; + newQueue->back = NULL; + newQueue->currentLength = 0; + newQueue->lock = false; + return newQueue; +} + +int dequeueInputMessage(inputMessageQueue * queue) +{ + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check the list isn't empty: + if(queue->front == NULL) + { + queue->lock = false; + return -1; + } + + // If there is only one item in the queue: + else if(queue->front == queue->back) + { + free(queue->front->content); + free(queue->front); + queue->front = NULL; + queue->back = NULL; + queue->currentLength--; + queue->lock = false; + return 0; + } + + // Remove the front item: + else + { + inputMessage * messageToDelete = queue->front; + queue->front = queue->front->next; + free(messageToDelete->content); + free(messageToDelete); + queue->currentLength--; + queue->lock = false; + return 0; + } +} + +int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer) +{ + // Copy the message into a new input message: + inputMessage * inputMessage = malloc(sizeof(inputMessage)); + + // Allocate the internal userMessage to store the message: + inputMessage->content = malloc(sizeof(userMessage)); + + // Copy the userMessage to the internal userMessage: + strncpy(inputMessage->content->senderName, messageToQueue.senderName, 32); + strncpy(inputMessage->content->messageContent, messageToQueue.messageContent, MAX); + + // We have no targets, NULL sends to all players in an area: + inputMessage->sender = sendingPlayer; + + // 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 = inputMessage; + queue->back = inputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + else + { + queue->back->next = inputMessage; + queue->back = inputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + } +} + +void userInputSanatize(char * inputString, int length) +{ + for(int index = 0; index <= length; index++) + { + if(!isprint(inputString[index])) + { + inputString[index] = '\n'; + inputString[index + 1] = '\0'; + break; + } + } + inputString[length - 1] = '\0'; +} + +// Return the front inputMessage from an inputMessageQueue: +inputMessage * peekInputMessage(inputMessageQueue * queue) +{ + return queue->front; +} + +outputMessage * peekOutputMessage(outputMessageQueue * queue) +{ + return queue->front; } diff --git a/src/misc/inputoutput.h b/src/misc/inputoutput.h index 71b1ad9..0726a01 100644 --- a/src/misc/inputoutput.h +++ b/src/misc/inputoutput.h @@ -1,21 +1,104 @@ // inputoutput.h: Header file contatning function prototypes and datastructures // for dealing with input and output. // Barry Kane, 2022. -#ifndef INPUTOUTPUt_H +#ifndef INPUTOUTPUT_H #define INPUTOUTPUT_H +#include +#include +#include +#include "constants.h" +#include "playerdata.h" #include // A message datastructure containing a user/character name and the content: typedef struct userMessage { char senderName[32]; - char messageContent[2048]; + char messageContent[MAX]; } userMessage; -// Sends a message to a given TLS session, wraps the calls to gnutls_write: -void messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); +// ================= +// -=[Message I/O]=- +// ================= -// Recieves a message from a given TLS session, wraps the calls to gnutls_read: -void messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); +// Sends a message to a given TLS session, wraps the calls to gnutls_write: +int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); + +// Receives a message from a given TLS session, wraps the calls to gnutls_read: +int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); + +// =================== +// -=[Output Queue]=-: +// =================== +typedef struct outputMessage outputMessage; +typedef struct outputMessage +{ + outputMessage * next; + playerInfo * targets[PLAYERCOUNT]; + userMessage * content; +} outputMessage; + +// A first-in first-out queue for message output to players: +typedef struct outputMessageQueue +{ + bool lock; + int currentLength; + outputMessage * back; + outputMessage * front; +} outputMessageQueue; + +// Creates and initializes a outputMessageQueue: +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); + +// Dequeue the front outputMessage from an outputMessageQueue: +int dequeueOutputMessage(outputMessageQueue * queue); + +// Return the front outputMessage from an outputMessageQueue: +outputMessage * peekOutputMessage(outputMessageQueue * queue); + +// ================== +// -=[Input Queue]=-: +// ================== +typedef struct inputMessage inputMessage; +typedef struct inputMessage +{ + inputMessage * next; + playerInfo * sender; + userMessage * content; +} inputMessage; + +// A first-in first-out queue for message input from players: +typedef struct inputMessageQueue +{ + bool lock; + int currentLength; + inputMessage * back; + inputMessage * front; +} inputMessageQueue; + +// Create a inputMessageQueue: +inputMessageQueue * createInputMessageQueue(void); + +// Enqueue a userMessage to an inputMessageQueue: +int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer); + +// Dequeue the front inputMessage from an inputMessageQueue: +int dequeueInputMessage(inputMessageQueue * queue); + +// Return the front inputMessage from an inputMessageQueue: +inputMessage * peekInputMessage(inputMessageQueue * queue); + +// ====================== +// -=[Input Sanitation]=- +// ====================== + +// Sanatize user input to ensure it's okay to send to the server: +void userInputSanatize(char * inputString, int length); #endif + diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h index f507e9f..9966544 100644 --- a/src/misc/playerdata.h +++ b/src/misc/playerdata.h @@ -1,6 +1,5 @@ // playerdata.h: Header file containing data structures for player data and function // prototypes for interacting with said data. -// Barry Kane, 2021. #ifndef PLAYERDATA_H #define PLAYERDATA_H #include @@ -33,7 +32,7 @@ int movePlayerToArea(playerInfo * player, char * requestedPath); // Create an area given a name and description: playerArea * createArea(char * nameString, char * descriptionString); -// Create a path between two areas given two areas and a string: -int createPath(playerArea * fromArea, playerArea * toArea, char * pathFromString, char * pathToString); +// Create a path between two areas given two areas and two strings: +int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); #endif diff --git a/src/tests/outputQueue-test.c b/src/tests/outputQueue-test.c new file mode 100644 index 0000000..9cc894d --- /dev/null +++ b/src/tests/outputQueue-test.c @@ -0,0 +1,35 @@ +#include "../misc/inputoutput.h" +#include +#include +int main() +{ + userMessage A, B, C; + strncpy(A.senderName, "Bob\0", 32 -1); + strncpy(A.messageContent, "joins the fray!\0", MAX-1); + strncpy(B.senderName, "Alice\0", 32 -1); + strncpy(B.messageContent, "challenges the unknown!\0", MAX -1); + strncpy(C.senderName, "Tom\0", 32 -1); + strncpy(C.messageContent, "Attacks them all!\0", MAX -1); + outputMessageQueue * testQueue = createOutputMessageQueue(); + printf("Queue Created.\n"); + printf("%d", queueOutputMessage(testQueue, A)); + printf("Queued A.\n"); + printf("%d", queueOutputMessage(testQueue, B)); + printf("Queued B.\n"); + printf("%d", queueOutputMessage(testQueue, C)); + printf("Queued C.\n"); + printf("%s\n", testQueue->front->content->senderName); + printf("%s\n", testQueue->front->content->messageContent); + printf("%s\n", testQueue->front->next->content->senderName); + printf("%s\n", testQueue->front->next->content->messageContent); + printf("%s\n", testQueue->front->next->next->content->senderName); + printf("%s\n", testQueue->front->next->next->content->messageContent); + printf("%s\n", testQueue->front->content->senderName); + dequeueOutputMessage(testQueue); + printf("%s\n", testQueue->front->content->senderName); + dequeueOutputMessage(testQueue); + printf("%s\n", testQueue->front->content->senderName); +// dequeueOutputMessage(testQueue); +// printf("%s\n", testQueue->front->content->senderName); + return 0; +} From 0b3a72beffb789f6d56799626207008890a78a40 Mon Sep 17 00:00:00 2001 From: Barry Date: Thu, 7 Apr 2022 01:39:59 +0100 Subject: [PATCH 15/53] Removed inputhandling library The functionality was moved to inputoutput. --- src/misc/inputhandling.c | 18 ------------------ src/misc/inputhandling.h | 10 ---------- 2 files changed, 28 deletions(-) delete mode 100644 src/misc/inputhandling.c delete mode 100644 src/misc/inputhandling.h diff --git a/src/misc/inputhandling.c b/src/misc/inputhandling.c deleted file mode 100644 index a90315a..0000000 --- a/src/misc/inputhandling.c +++ /dev/null @@ -1,18 +0,0 @@ -// inputhandling.c: Implementation of input handling library for SilverMUD. -// Barry Kane, 2021. -#include - -void userInputSanatize(char * inputString, int length) -{ - for(int index = 0; index <= length; index++) - { - if(!isprint(inputString[index])) - { - inputString[index] = '\n'; - inputString[index + 1] = '\0'; - break; - } - } - inputString[length - 1] = '\0'; -} - diff --git a/src/misc/inputhandling.h b/src/misc/inputhandling.h deleted file mode 100644 index 95c9bca..0000000 --- a/src/misc/inputhandling.h +++ /dev/null @@ -1,10 +0,0 @@ -// inputhandling.h: Header file for the inputhandling library for SilverMUD. -// Barry Kane, 2021 -#ifndef INPUTHANDLING_H -#define INPUTHANDLING_H -#include - -// Sanatize user input to ensure it's okay to send to the server: -void userInputSanatize(char * inputString, int length); - -#endif From 151f3002b81d4629b656598ba957ae401503bae0 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Thu, 5 May 2022 19:45:27 +0100 Subject: [PATCH 16/53] Began implementing game logic and re-implementing commands - Reimplemented /MOVE and /EXIT - The server is now multi-threaded - Input and output is now queued --- src/SilverMUDClient.c | 3 +- src/SilverMUDServer.c | 81 +++++++++++++++++++++++++++-------- src/misc/gamelogic.c | 95 ++++++++++++++++++++++++++++++++++++++++++ src/misc/gamelogic.h | 26 ++++++++++++ src/misc/inputoutput.c | 89 +++++++++++++++++++++++++++++++++------ src/misc/inputoutput.h | 16 +++---- 6 files changed, 271 insertions(+), 39 deletions(-) create mode 100644 src/misc/gamelogic.c create mode 100644 src/misc/gamelogic.h 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); From 8673bb1ad5391e0e61f7b3ebf734ef74c01c0ef5 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 20 May 2022 22:28:07 +0100 Subject: [PATCH 17/53] Reorganized file structure. - Reimplemented /LOOK. - Commands are now accepted in both upper and lower case. - Move now accepts a number for easier movement. --- Makefile | 8 +-- src/SilverMUD.org => SilverMUD.org | 0 {src/misc => include}/constants.h | 0 {src/misc => include}/gamelogic.h | 0 {src/misc => include}/inputoutput.h | 0 {src/misc => include}/lists.h | 0 {src/misc => include}/playerdata.h | 0 {src/misc => include}/texteffects.h | 0 src/{ => client}/SilverMUDClient.c | 11 ++-- src/{misc => }/gamelogic.c | 68 +++++++++++++++++++++---- src/{misc => }/inputoutput.c | 8 +-- src/{misc => }/lists.c | 4 +- src/{misc => }/playerdata.c | 18 ++++++- src/{ => server}/SilverMUDServer.c | 12 ++--- src/{misc => }/texteffects.c | 0 {src/tests => tests}/list-test.c | 0 {src/tests => tests}/outputQueue-test.c | 0 17 files changed, 98 insertions(+), 31 deletions(-) rename src/SilverMUD.org => SilverMUD.org (100%) rename {src/misc => include}/constants.h (100%) rename {src/misc => include}/gamelogic.h (100%) rename {src/misc => include}/inputoutput.h (100%) rename {src/misc => include}/lists.h (100%) rename {src/misc => include}/playerdata.h (100%) rename {src/misc => include}/texteffects.h (100%) rename src/{ => client}/SilverMUDClient.c (96%) rename src/{misc => }/gamelogic.c (51%) rename src/{misc => }/inputoutput.c (98%) rename src/{misc => }/lists.c (97%) rename src/{misc => }/playerdata.c (85%) rename src/{ => server}/SilverMUDServer.c (97%) rename src/{misc => }/texteffects.c (100%) rename {src/tests => tests}/list-test.c (100%) rename {src/tests => tests}/outputQueue-test.c (100%) diff --git a/Makefile b/Makefile index a945a26..30cd661 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ CC = gcc -clientsrc = $(wildcard src/misc/*.c) \ - src/SilverMUDClient.c +clientsrc = $(wildcard src/*.c) \ + src/client/SilverMUDClient.c clientobj = $(clientsrc:.c=.o) -serversrc = $(wildcard src/misc/*.c) \ - src/SilverMUDServer.c +serversrc = $(wildcard src/*.c) \ + src/server/SilverMUDServer.c serverobj = $(serversrc:.c=.o) CLIENTLDFLAGS= -lpthread -lncurses -lgnutls SERVERLDFLAGS= -lpthread -lncurses -lgnutls diff --git a/src/SilverMUD.org b/SilverMUD.org similarity index 100% rename from src/SilverMUD.org rename to SilverMUD.org diff --git a/src/misc/constants.h b/include/constants.h similarity index 100% rename from src/misc/constants.h rename to include/constants.h diff --git a/src/misc/gamelogic.h b/include/gamelogic.h similarity index 100% rename from src/misc/gamelogic.h rename to include/gamelogic.h diff --git a/src/misc/inputoutput.h b/include/inputoutput.h similarity index 100% rename from src/misc/inputoutput.h rename to include/inputoutput.h diff --git a/src/misc/lists.h b/include/lists.h similarity index 100% rename from src/misc/lists.h rename to include/lists.h diff --git a/src/misc/playerdata.h b/include/playerdata.h similarity index 100% rename from src/misc/playerdata.h rename to include/playerdata.h diff --git a/src/misc/texteffects.h b/include/texteffects.h similarity index 100% rename from src/misc/texteffects.h rename to include/texteffects.h diff --git a/src/SilverMUDClient.c b/src/client/SilverMUDClient.c similarity index 96% rename from src/SilverMUDClient.c rename to src/client/SilverMUDClient.c index a4017de..119baed 100644 --- a/src/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -13,10 +13,10 @@ #include #include #include -#include "misc/constants.h" -#include "misc/playerdata.h" -#include "misc/texteffects.h" -#include "misc/inputoutput.h" +#include "../../include/constants.h" +#include "../../include/playerdata.h" +#include "../../include/texteffects.h" +#include "../../include/inputoutput.h" // A struct for passing arguments to our threads containing a file descriptor and a window pointer: typedef struct threadparameters @@ -85,7 +85,8 @@ void * messageReceiver(void * parameters) shouldExit = true; pthread_exit(NULL); } - slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window, true); + slowPrintNcurses("\n --====<", 8000, threadParameters->window, true); + slowPrintNcurses(">====-- \n", 8000, threadParameters->window, true); slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window, false); slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window, true); } diff --git a/src/misc/gamelogic.c b/src/gamelogic.c similarity index 51% rename from src/misc/gamelogic.c rename to src/gamelogic.c index 15efcd5..0229d0b 100644 --- a/src/misc/gamelogic.c +++ b/src/gamelogic.c @@ -1,10 +1,11 @@ // gamelogic.c: Contains function definitons for dealing with the game's logic. // Barry Kane, 2022. +#include #include -#include "constants.h" -#include "gamelogic.h" -#include "playerdata.h" -#include "inputoutput.h" +#include "../include/constants.h" +#include "../include/gamelogic.h" +#include "../include/playerdata.h" +#include "../include/inputoutput.h" // ======================= // -=[ Main Game Loop ]=-: @@ -13,11 +14,13 @@ // Thread function which runs the main game loop, given the needed parameters: void * gameLogicLoop(void * parameters) { + char formattedString[64]; gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; bool keepRunning = true; while(keepRunning) { + // Check for new messages and pop them off the queue: if(threadParameters->inputQueue->currentLength != 0) { while(threadParameters->inputQueue->lock == true) @@ -26,11 +29,15 @@ void * gameLogicLoop(void * parameters) } currentInput = peekInputMessage(threadParameters->inputQueue); userInputSanatize(currentInput->content->messageContent, MAX); + // A slash as the first character means the message is a user command: if(currentInput->content->messageContent[0] == '/') { // TODO: Implement Command Queue. - // For now, basic intepretation will do. - if(strncmp(¤tInput->content->messageContent[1], "EXIT", 4) == 0) + // For now, basic intepretation will do. + + // Exit command: Sends an "empty" exit message to disconnect a client: + if(strncmp(¤tInput->content->messageContent[1], "EXIT", 4) == 0 || + strncmp(¤tInput->content->messageContent[1], "exit", 4) == 0) { userMessage * exitMessage = malloc(sizeof(userMessage)); exitMessage->senderName[0] = '\0'; @@ -38,9 +45,13 @@ void * gameLogicLoop(void * parameters) queueTargetedOutputMessage(threadParameters->outputQueue, exitMessage, ¤tInput->sender, 1); free(exitMessage); } - if(strncmp(¤tInput->content->messageContent[1], "MOVE", 4) == 0) + + // Move command: Moves the current player down a path in their current area, given a pathname or number: + if(strncmp(¤tInput->content->messageContent[1], "MOVE", 4) == 0 || + strncmp(¤tInput->content->messageContent[1], "move", 4) == 0) { userMessage * moveMessage = malloc(sizeof(userMessage)); + bzero(moveMessage, sizeof(userMessage)); char requestedPath[32]; strncpy(requestedPath, ¤tInput->content->messageContent[6], 32); userInputSanatize(requestedPath, 32); @@ -55,12 +66,51 @@ void * gameLogicLoop(void * parameters) requestedPath[31] = '\0'; if(movePlayerToArea(currentInput->sender, requestedPath) == 0) { - strcpy(moveMessage->senderName, "\0"); - strcpy(moveMessage->messageContent, currentInput->sender->currentArea->areaDescription); + moveMessage->senderName[0] = '\0'; + strncat(moveMessage->messageContent, currentInput->sender->currentArea->areaName, 33); + strncat(moveMessage->messageContent, "\n", 2); + strncat(moveMessage->messageContent, currentInput->sender->currentArea->areaDescription, 256); + strncat(moveMessage->messageContent, "\nYou can go:", 13); + for(int index = 0; index < 16; index++) + { + if(currentInput->sender->currentArea->areaExits[index] != NULL) + { + snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentInput->sender->currentArea->areaExits[index]->pathName); + strncat(moveMessage->messageContent, formattedString, 64); + } + } queueTargetedOutputMessage(threadParameters->outputQueue, moveMessage, ¤tInput->sender, 1); } free(moveMessage); } + + // Look command: Sends the current area's name, description, and + if(strncmp(¤tInput->content->messageContent[1], "LOOK", 4) == 0 || + strncmp(¤tInput->content->messageContent[1], "look", 4) == 0) + { + userMessage * lookMessage = malloc(sizeof(userMessage)); + strncat(lookMessage->messageContent, currentInput->sender->currentArea->areaName, 33); + strncat(lookMessage->messageContent, "\n", 2); + strncat(lookMessage->messageContent, currentInput->sender->currentArea->areaDescription, 256); + strncat(lookMessage->messageContent, "\nYou can go:", 13); + for(int index = 0; index < 16; index++) + { + if(currentInput->sender->currentArea->areaExits[index] != NULL) + { + snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentInput->sender->currentArea->areaExits[index]->pathName); + strncat(lookMessage->messageContent, formattedString, 64); + } + } + queueTargetedOutputMessage(threadParameters->outputQueue, lookMessage, ¤tInput->sender, 1); + free(lookMessage); + } + + // Name command: Checks if the name is isn't used and is valid, then changes the player's name: + if(strncmp(¤tInput->content->messageContent[1], "NAME", 4) == 0 || + strncmp(¤tInput->content->messageContent[1], "name", 4) == 0) + { + + } } else { diff --git a/src/misc/inputoutput.c b/src/inputoutput.c similarity index 98% rename from src/misc/inputoutput.c rename to src/inputoutput.c index c6f0739..f2caa6a 100644 --- a/src/misc/inputoutput.c +++ b/src/inputoutput.c @@ -4,11 +4,11 @@ #include #include #include -#include -#include "constants.h" -#include "playerdata.h" -#include "inputoutput.h" +#include #include +#include "../include/constants.h" +#include "../include/playerdata.h" +#include "../include/inputoutput.h" // Sends a message to a given TLS session, wraps the calls to gnutls_write: int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) diff --git a/src/misc/lists.c b/src/lists.c similarity index 97% rename from src/misc/lists.c rename to src/lists.c index c85ab0b..a7379d0 100644 --- a/src/misc/lists.c +++ b/src/lists.c @@ -1,7 +1,7 @@ // Implementation of lists library for SilverMUD. // Barry Kane, 2021 -#include "lists.h" -#include "playerdata.h" +#include "../include/lists.h" +#include "../include/playerdata.h" areaNode * createAreaList(playerArea * initialArea) { diff --git a/src/misc/playerdata.c b/src/playerdata.c similarity index 85% rename from src/misc/playerdata.c rename to src/playerdata.c index e2f5d88..b4f52c7 100644 --- a/src/misc/playerdata.c +++ b/src/playerdata.c @@ -3,11 +3,27 @@ #include #include #include -#include "playerdata.h" +#include "../include/playerdata.h" // Move a player to a different area given a path in the area: int movePlayerToArea(playerInfo * player, char * requestedPath) { + // Check if a number was given first: + int selected = atoi(requestedPath); + if(selected != 0) + { + if(player->currentArea->areaExits[selected -1]->areaToJoin != NULL) + { + player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin; + return 0; + } + else + { + return 1; + } + } + + // Otherwise search for the description: for (int index = 0; index < 16; index++) { if(player->currentArea->areaExits[index] != NULL) diff --git a/src/SilverMUDServer.c b/src/server/SilverMUDServer.c similarity index 97% rename from src/SilverMUDServer.c rename to src/server/SilverMUDServer.c index 37aae8e..be97ab7 100644 --- a/src/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -14,12 +14,12 @@ #include #include #include -#include "misc/lists.h" -#include "misc/gamelogic.h" -#include "misc/constants.h" -#include "misc/playerdata.h" -#include "misc/texteffects.h" -#include "misc/inputoutput.h" +#include "../../include/lists.h" +#include "../../include/gamelogic.h" +#include "../../include/constants.h" +#include "../../include/playerdata.h" +#include "../../include/texteffects.h" +#include "../../include/inputoutput.h" typedef struct sockaddr sockaddr; diff --git a/src/misc/texteffects.c b/src/texteffects.c similarity index 100% rename from src/misc/texteffects.c rename to src/texteffects.c diff --git a/src/tests/list-test.c b/tests/list-test.c similarity index 100% rename from src/tests/list-test.c rename to tests/list-test.c diff --git a/src/tests/outputQueue-test.c b/tests/outputQueue-test.c similarity index 100% rename from src/tests/outputQueue-test.c rename to tests/outputQueue-test.c From b8189ae2dee1ffd4f44faa18c3482c3718a73da2 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 16 Oct 2022 16:13:33 +0100 Subject: [PATCH 18/53] Began implementation of skills and stats. - Added text wrapping in client. - Implemented functions for managing skill data. - Rewrote some existing functionality to allow for variable length game messages over multiple userMessages. - Reorganized the code yet again. - Introduced enums for coreStats and outcomes. - Implemented core stat checks. - Added more example skills. - Rewrote test areas to have longer descriptions. --- Makefile | 2 +- SilverMUD.org | 20 + include/gamelogic.h | 26 -- include/playerdata.h | 38 -- src/client/SilverMUDClient.c | 50 ++- {include => src}/constants.h | 0 src/gamelogic.c | 741 +++++++++++++++++++++++++++++---- src/gamelogic.h | 89 ++++ src/inputoutput.c | 23 +- {include => src}/inputoutput.h | 5 +- src/lists.c | 8 +- {include => src}/lists.h | 0 src/playerdata.c | 320 +++++++++++++- src/playerdata.h | 114 +++++ src/server/SilverMUDServer.c | 125 ++++-- src/texteffects.c | 58 +++ {include => src}/texteffects.h | 1 + tests/corestat-from-string.c | 11 + tests/list-test.c | 4 +- tests/outputQueue-test.c | 2 +- 20 files changed, 1407 insertions(+), 230 deletions(-) delete mode 100644 include/gamelogic.h delete mode 100644 include/playerdata.h rename {include => src}/constants.h (100%) create mode 100644 src/gamelogic.h rename {include => src}/inputoutput.h (96%) rename {include => src}/lists.h (100%) create mode 100644 src/playerdata.h rename {include => src}/texteffects.h (95%) create mode 100644 tests/corestat-from-string.c diff --git a/Makefile b/Makefile index 30cd661..01d6e91 100644 --- a/Makefile +++ b/Makefile @@ -25,5 +25,5 @@ clean: all: SilverMUDClient SilverMUDServer -debug: CFLAGS += -Wall -ggdb +debug: CFLAGS += -Wall -ggdb -Wextra debug: SilverMUDClientDebug SilverMUDServerDebug diff --git a/SilverMUD.org b/SilverMUD.org index 30aa5c8..070bfe8 100644 --- a/SilverMUD.org +++ b/SilverMUD.org @@ -4,5 +4,25 @@ world through the internet. It's designed to give a gamemaster the same power to improvise that they have at the table, through simple programming and easy-to-understand structures. ** Player's Guide +*** The Basic Commands +SilverMUD is played through a set of very simple commands. To use a command, +type a forward-slash (/) followed immediately by the command name. The command +can be upper or lower-case. + +| Command | Arguments | Effect | +|---------+---------------------------------------------------+--------------------------------------------------------------------| +| JOIN | Takes a character name | Logs you into the server with the given character name. | +| MOVE | Takes a path name or a path number | Moves you down the given path. | +| LOOK | None | Gives you a description of what's around you, and what you can do. | +| STAT | None | Displays your current status and character sheet. | +| SPEC | Core stat name | Allows you to apply spec points to a given stat. | +| TRY | Core stat name or skill name and an object number | Attempt to use the given stat or skill on the object. | + ** Gamemaster's Guide +*** Running the Server: + ** Developer's Guide +*** Build Prerequisites: +SilverMUD has the following dependencies: +- GnuTLS +- ncurses diff --git a/include/gamelogic.h b/include/gamelogic.h deleted file mode 100644 index be391ae..0000000 --- a/include/gamelogic.h +++ /dev/null @@ -1,26 +0,0 @@ -// 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/include/playerdata.h b/include/playerdata.h deleted file mode 100644 index 9966544..0000000 --- a/include/playerdata.h +++ /dev/null @@ -1,38 +0,0 @@ -// playerdata.h: Header file containing data structures for player data and function -// prototypes for interacting with said data. -#ifndef PLAYERDATA_H -#define PLAYERDATA_H -#include - -typedef struct playerPath playerPath; -typedef struct playerArea playerArea; - -struct playerPath -{ - char pathName[32]; - playerArea * areaToJoin; -}; - -struct playerArea -{ - char areaName[32]; - char areaDescription[256]; - playerPath * areaExits[16]; -}; - -typedef struct playerInfo -{ - char playerName[32]; - playerArea * currentArea; -} playerInfo; - -// Move a player to a different area given a path in the area: -int movePlayerToArea(playerInfo * player, char * requestedPath); - -// Create an area given a name and description: -playerArea * createArea(char * nameString, char * descriptionString); - -// Create a path between two areas given two areas and two strings: -int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); - -#endif diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index 119baed..57c57a1 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -13,12 +13,12 @@ #include #include #include -#include "../../include/constants.h" -#include "../../include/playerdata.h" -#include "../../include/texteffects.h" -#include "../../include/inputoutput.h" +#include "../constants.h" +#include "../playerdata.h" +#include "../texteffects.h" +#include "../inputoutput.h" -// A struct for passing arguments to our threads containing a file descriptor and a window pointer: +// A struct for bundling all needed paramaters for a thread so we can pass them using a void pointer: typedef struct threadparameters { gnutls_session_t tlsSession; @@ -72,26 +72,37 @@ void * messageSender(void * parameters) void * messageReceiver(void * parameters) { struct threadparameters *threadParameters = parameters; + bool serverMessage = false; userMessage receiveBuffer; - + int screenWidth = getmaxx(threadParameters->window); + // Repeatedly take messages from the server and print them to the chat log window: while (!shouldExit) { messageReceive(threadParameters->tlsSession, &receiveBuffer); if (receiveBuffer.senderName[0] == '\0') { + wrapString(receiveBuffer.messageContent, + strlen(receiveBuffer.messageContent) - 1, screenWidth); if (receiveBuffer.messageContent[0] == '\0') { shouldExit = true; pthread_exit(NULL); } - slowPrintNcurses("\n --====<", 8000, threadParameters->window, true); - slowPrintNcurses(">====-- \n", 8000, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window, false); - slowPrintNcurses("\n --====<>====-- \n", 8000, threadParameters->window, true); + if(serverMessage == false) + { + slowPrintNcurses("\n --====<>====--", 4000, threadParameters->window, true); + serverMessage = true; + } + slowPrintNcurses("\n", 4000, threadParameters->window, true); + slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); + slowPrintNcurses("\n", 4000, threadParameters->window, true); } else { + wrapString(receiveBuffer.messageContent, + strlen(receiveBuffer.messageContent) - 1, + screenWidth - strlen(receiveBuffer.senderName) - 2); if (threadParameters->loggingFlag == true) { fputs(receiveBuffer.senderName, threadParameters->loggingStream); @@ -99,9 +110,14 @@ void * messageReceiver(void * parameters) fputs(receiveBuffer.messageContent, threadParameters->loggingStream); fflush(threadParameters->loggingStream); } - slowPrintNcurses(receiveBuffer.senderName, 8000, threadParameters->window, true); - slowPrintNcurses(": ", 8000, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, 8000, threadParameters->window, false); + if(serverMessage == true) + { + slowPrintNcurses("\n --====<>====-- \n", 4000, threadParameters->window, true); + serverMessage = false; + } + slowPrintNcurses(receiveBuffer.senderName, 4000, threadParameters->window, true); + slowPrintNcurses(": ", 4000, threadParameters->window, true); + slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); } } pthread_exit(NULL); @@ -115,7 +131,7 @@ int main(int argc, char **argv) pthread_t receivingThread; int port = 5000; int currentopt = 0; - int characterDelay = 8000; + int characterDelay = 4000; char chatLogPath[PATH_MAX + 1]; char gameLogPath[PATH_MAX + 1]; char ipAddress[32] = "127.0.0.1"; @@ -248,7 +264,7 @@ int main(int argc, char **argv) logArea = malloc(sizeof(*logArea)); messageArea = malloc(sizeof(*messageArea)); - + // Make the windows for the structs, and pass the socket descriptor: logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); logArea->tlsSession = tlsSession; @@ -257,7 +273,7 @@ int main(int argc, char **argv) { logArea->loggingStream = chatLog; } - messageArea->window = newwin(3, COLS, LINES - 3, 0); + messageArea->window = newwin(3, COLS - 2, LINES - 4, 1); messageArea->tlsSession = tlsSession; messageArea->loggingFlag = gameLogging; if (gameLog != NULL) @@ -269,7 +285,7 @@ int main(int argc, char **argv) scrollok(logArea->window, true); scrollok(messageArea->window, true); - // Run a thread to send messages, and use main to recieve: + // Run a thread to send messages, and use another to recieve: pthread_create(&sendingThread, NULL, messageSender, messageArea); pthread_create(&receivingThread, NULL, messageReceiver, logArea); diff --git a/include/constants.h b/src/constants.h similarity index 100% rename from include/constants.h rename to src/constants.h diff --git a/src/gamelogic.c b/src/gamelogic.c index 0229d0b..3161d86 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -1,11 +1,13 @@ // gamelogic.c: Contains function definitons for dealing with the game's logic. // Barry Kane, 2022. #include +#include #include -#include "../include/constants.h" -#include "../include/gamelogic.h" -#include "../include/playerdata.h" -#include "../include/inputoutput.h" +#include +#include "constants.h" +#include "gamelogic.h" +#include "playerdata.h" +#include "inputoutput.h" // ======================= // -=[ Main Game Loop ]=-: @@ -14,103 +16,34 @@ // Thread function which runs the main game loop, given the needed parameters: void * gameLogicLoop(void * parameters) { - char formattedString[64]; gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; bool keepRunning = true; + commandQueue * commandQueue = createCommandQueue(); while(keepRunning) { + // Evaluate remaining commands: + if(commandQueue->currentLength != 0) + { + evaluateNextCommand(threadParameters, commandQueue); + } // Check for new messages and pop them off the queue: if(threadParameters->inputQueue->currentLength != 0) { - while(threadParameters->inputQueue->lock == true) - { - threadParameters->inputQueue->lock = true; - } + while(threadParameters->inputQueue->lock == true); + threadParameters->inputQueue->lock = true; currentInput = peekInputMessage(threadParameters->inputQueue); userInputSanatize(currentInput->content->messageContent, MAX); // A slash as the first character means the message is a user command: if(currentInput->content->messageContent[0] == '/') { - // TODO: Implement Command Queue. - // For now, basic intepretation will do. - - // Exit command: Sends an "empty" exit message to disconnect a client: - if(strncmp(¤tInput->content->messageContent[1], "EXIT", 4) == 0 || - 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); - } - - // Move command: Moves the current player down a path in their current area, given a pathname or number: - if(strncmp(¤tInput->content->messageContent[1], "MOVE", 4) == 0 || - strncmp(¤tInput->content->messageContent[1], "move", 4) == 0) - { - userMessage * moveMessage = malloc(sizeof(userMessage)); - bzero(moveMessage, 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) - { - moveMessage->senderName[0] = '\0'; - strncat(moveMessage->messageContent, currentInput->sender->currentArea->areaName, 33); - strncat(moveMessage->messageContent, "\n", 2); - strncat(moveMessage->messageContent, currentInput->sender->currentArea->areaDescription, 256); - strncat(moveMessage->messageContent, "\nYou can go:", 13); - for(int index = 0; index < 16; index++) - { - if(currentInput->sender->currentArea->areaExits[index] != NULL) - { - snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentInput->sender->currentArea->areaExits[index]->pathName); - strncat(moveMessage->messageContent, formattedString, 64); - } - } - queueTargetedOutputMessage(threadParameters->outputQueue, moveMessage, ¤tInput->sender, 1); - } - free(moveMessage); - } - - // Look command: Sends the current area's name, description, and - if(strncmp(¤tInput->content->messageContent[1], "LOOK", 4) == 0 || - strncmp(¤tInput->content->messageContent[1], "look", 4) == 0) - { - userMessage * lookMessage = malloc(sizeof(userMessage)); - strncat(lookMessage->messageContent, currentInput->sender->currentArea->areaName, 33); - strncat(lookMessage->messageContent, "\n", 2); - strncat(lookMessage->messageContent, currentInput->sender->currentArea->areaDescription, 256); - strncat(lookMessage->messageContent, "\nYou can go:", 13); - for(int index = 0; index < 16; index++) - { - if(currentInput->sender->currentArea->areaExits[index] != NULL) - { - snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentInput->sender->currentArea->areaExits[index]->pathName); - strncat(lookMessage->messageContent, formattedString, 64); - } - } - queueTargetedOutputMessage(threadParameters->outputQueue, lookMessage, ¤tInput->sender, 1); - free(lookMessage); - } - - // Name command: Checks if the name is isn't used and is valid, then changes the player's name: - if(strncmp(¤tInput->content->messageContent[1], "NAME", 4) == 0 || - strncmp(¤tInput->content->messageContent[1], "name", 4) == 0) - { - - } + queueMessagedCommand(commandQueue, currentInput); + } + else if(currentInput->sender->currentArea == getAreaFromList(threadParameters->areaList, 0)) + { + currentInput = NULL; + threadParameters->inputQueue->lock = false; + dequeueInputMessage(threadParameters->inputQueue); } else { @@ -143,3 +76,635 @@ void * gameLogicLoop(void * parameters) } return NULL; } + +// Create a commandQueue: +commandQueue * createCommandQueue(void) +{ + commandQueue * newCommandQueue = calloc(1, sizeof(commandQueue)); + newCommandQueue->back = NULL; + newCommandQueue->front = NULL; + newCommandQueue->lock = false; + newCommandQueue->paused = false; + newCommandQueue->currentLength = 0; + return newCommandQueue; +} + +// Return the front commandEvent from a commandQueue: +commandEvent * peekCommand(commandQueue * queue) +{ + // Do nothing until the command queue is unlocked. + while(queue->lock); + + // Return the front item. + return queue->front; +} + +// Enqueue a messaged command to a commandQueue: +int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) +{ + // Prepare the new commandEvent: + commandEvent * newCommand = calloc(1, sizeof(commandEvent)); + newCommand->command = calloc(16, sizeof(char)); + newCommand->arguments = calloc(MAX, sizeof(char)); + newCommand->caller = messageToQueue->sender; + + // Seperate the command from it's arguments: + strtok(messageToQueue->content->messageContent, " "); + + // Copy the command and arguments to the new commandEvent: + strncpy(newCommand->command, &messageToQueue->content->messageContent[1], 16); + strncpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2], + MAX - (strlen(newCommand->command) + 2)); + + // Ensure the arguments are safe to parse, without adding newlines: + userNameSanatize(newCommand->command, 16); + userNameSanatize(newCommand->arguments, MAX); + + // Lowercase the command for easier comparison: + for (char * character = newCommand->command; *character; ++character) + { + *character = tolower(*character); + } + + // Wait for the queue to unlock: + while (queue->lock); + + // 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 commandEvent as both the front and back of the queue: + if(queue->front == NULL) + { + queue->front = newCommand; + queue->back = newCommand; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + else + { + queue->back->next = newCommand; + queue->back = newCommand; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + } +} + +// Enqueue a command to a commandQueue: +int queueCommand(commandQueue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer) +{ + // Prepare the new commandEvent: + commandEvent * newCommand = calloc(1, sizeof(commandEvent)); + newCommand->command = calloc(16, sizeof(char)); + newCommand->arguments = calloc(MAX, sizeof(char)); + newCommand->caller = callingPlayer; + + // Copy the command and arguments: + strncpy(newCommand->command, command, commandLength); + strncpy(newCommand->arguments, arguments, argumentsLength); + + // Ensure the arguments are safe to parse, without adding newlines: + userNameSanatize(newCommand->command, 16); + + // Wait for the queue to unlock: + while (queue->lock); + + // 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 commandEvent as both the front and back of the queue: + if(queue->front == NULL) + { + queue->front = newCommand; + queue->back = newCommand; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + else + { + queue->back->next = newCommand; + queue->back = newCommand; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + } +} + +// Dequeue the front commandEvent from a commandQueue: +int dequeueCommand(commandQueue * queue) +{ + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check the list isn't empty: + if(queue->front == NULL) + { + queue->lock = false; + return -1; + } + + // If there is only one item in the queue: + else if(queue->front == queue->back) + { + free(queue->front->command); + free(queue->front->arguments); + free(queue->front); + queue->front = NULL; + queue->back = NULL; + queue->currentLength--; + queue->lock = false; + return 0; + } + + // Remove the front item: + else + { + commandEvent * commandToDelete = queue->front; + queue->front = queue->front->next; + free(commandToDelete->command); + free(commandToDelete->arguments); + free(commandToDelete); + queue->currentLength--; + queue->lock = false; + return 0; + } +} + +// Evaluate the next commandEvent: +int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) +{ + commandEvent * currentCommand = peekCommand(queue); + while(queue->lock); + queue->lock = true; + if(currentCommand == NULL) + { + return -1; + } + // Try command: Attempt to use a stat or skill on an object: + if(strncmp(currentCommand->command, "try", 3) == 0) + { + userMessage * tryMessage = malloc(sizeof(userMessage)); + tryMessage->senderName[0] = '\0'; + switch (getCoreStatFromString(currentCommand->arguments, 9)) + { + case STRENGTH: + { + switch (statCheck(currentCommand->caller, 20, STRENGTH)) + { + case CRITICAL_FAILURE: + { + strcpy(tryMessage->messageContent, "You weak, puny shit. Bet you don't even lift, bro.\n"); + break; + } + case FAILURE: + { + strcpy(tryMessage->messageContent, "Come on, bro, you should be able to get this set done.\n"); + break; + } + case SUCCESS: + { + strcpy(tryMessage->messageContent, "Nice set, bro. Keep it up.\n"); + break; + } + case CRITICAL_SUCCESS: + { + strcpy(tryMessage->messageContent, "HOLY SHIT, BRO! THAT'S SOME MAD REPS RIGHT THERE!\n"); + break; + } + default: + { + strcpy(tryMessage->messageContent, "I don't even, bro.\n"); + } + } + break; + } + default: + { + strcpy(tryMessage->messageContent, "Not at the moment, mate.\n"); + break; + } + } + queueTargetedOutputMessage(parameters->outputQueue, tryMessage, ¤tCommand->caller, 1); + free(tryMessage); + } + // Exit command: Sends an "empty" exit message to disconnect a client: + if(strncmp(currentCommand->command, "exit", 4) == 0) + { + userMessage * exitMessage = malloc(sizeof(userMessage)); + exitMessage->senderName[0] = '\0'; + exitMessage->messageContent[0] = '\0'; + queueTargetedOutputMessage(parameters->outputQueue, exitMessage, ¤tCommand->caller, 1); + free(exitMessage); + } + + // Move command: Moves the caller to a different area given a path name or number: + if(strncmp(currentCommand->command, "move", 4) == 0) + { + char requestedPath[32]; + if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getAreaFromList(parameters->areaList, 0)) + { + strncpy(requestedPath, currentCommand->arguments, 32); + userNameSanatize(requestedPath, 32); + requestedPath[31] = '\0'; + if(movePlayerToArea(currentCommand->caller, requestedPath) == 0) + { + // Call the look command after moving. It's fine to unlock, because the loop won't + // continue until the command is queued: + queue->lock = false; + queueCommand(queue, "look", "", 5, 0, currentCommand->caller); + queue->lock = true; + } + } + } + + // Look command: Returns the description of the current area and paths: + if(strncmp(currentCommand->command, "look", 4) == 0) + { + char formattedString[64]; + userMessage * lookMessage = calloc(1, sizeof(userMessage)); + lookMessage->senderName[0] = '\0'; + strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33); + strncat(lookMessage->messageContent, "\n", 2); + strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35); + queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + bzero(lookMessage, sizeof(userMessage)); + if(currentCommand->caller->currentArea->areaExits[0] != NULL) + { + strncat(lookMessage->messageContent, "You can go:", 13); + for(int index = 0; index < 16; index++) + { + if(currentCommand->caller->currentArea->areaExits[index] != NULL) + { + snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentCommand->caller->currentArea->areaExits[index]->pathName); + strncat(lookMessage->messageContent, formattedString, 64); + } + } + queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + } + free(lookMessage); + } + // Join command: Allows the player to join the game given a name: + // TODO: Implement login/character creation. Will be a while: + if(strncmp(currentCommand->command, "join", 4) == 0) + { + if(currentCommand->caller->currentArea == getAreaFromList(parameters->areaList, 0)) + { + bool validName = true; + for(int index = 0; index < *parameters->playerCount; index++) + { + if(currentCommand->arguments[0] == '\0') + { + validName = false; + } + if(strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) + { + validName = false; + } + } + if(validName) + { + strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16); + currentCommand->caller->currentArea = getAreaFromList(parameters->areaList, 1); + // Call the look command after joining. It's fine to unlock, because the loop won't + // continue until the command is queued: + queue->lock = false; + queueCommand(queue, "look", "", 5, 0, currentCommand->caller); + queue->lock = true; + } + } + } + // Talk command: Allows the player to begin a chat session with another player: + if(strncmp(currentCommand->command, "talk", 4) == 0) + { + // TODO: Implement. + } + if(strncmp(currentCommand->command, "skillissue", 10) == 0) + { + userMessage * statMessage = calloc(1, sizeof(userMessage)); + statMessage->senderName[0] = '\0'; + strcpy(statMessage->messageContent, "Have you tried getting good?"); + queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + free(statMessage); + } + // Stat command: Displays the current character's sheet. + if(strncmp(currentCommand->command, "stat", 4) == 0) + { + char * formattedString = calloc(121, sizeof(char)); + userMessage * statMessage = calloc(1, sizeof(userMessage)); + statMessage->senderName[0] = '\0'; + // Basic status: Name, level, location. + snprintf(formattedString, 120, "%s, Level %d | %s\n", currentCommand->caller->playerName, + currentCommand->caller->stats->level, currentCommand->caller->currentArea->areaName); + strncat(statMessage->messageContent, formattedString, 120); + + // Current stats: Health and WISED. + snprintf(formattedString, 120, + "Health: %d/%d\nStats:\n\tWits: %2d | Intellect: %2d | Strength: %2d | Endurance: %2d | Dexerity: %2d \n", + currentCommand->caller->stats->currentHealth, currentCommand->caller->stats->maxHealth, + currentCommand->caller->stats->wits, currentCommand->caller->stats->intellect, + currentCommand->caller->stats->strength, currentCommand->caller->stats->endurance, + currentCommand->caller->stats->dexerity); + strncat(statMessage->messageContent, formattedString, 120); + + // Levelling stats: Current XP, and spec points. + if(currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) + { + snprintf(formattedString, 120, "Current Experience: %ld | Spec Points Available: %d | Skill Points Available: %d", + currentCommand->caller->stats->experience, currentCommand->caller->stats->specPoints, currentCommand->caller->stats->skillPoints); + } + else + { + snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience); + } + strncat(statMessage->messageContent, formattedString, 120); + + queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + bzero(statMessage->messageContent, sizeof(char) * MAX); + if(currentCommand->caller->skills->head != NULL) + { + skillNode * currentSkill = currentCommand->caller->skills->head; + int charCount = 0; + bool addNewline = false; + while(currentSkill != NULL) + { + snprintf(formattedString, 120, "| %2d | %31s ", + currentSkill->skill->skillPoints, currentSkill->skill->skillName); + charCount += 43; + strncat(statMessage->messageContent, formattedString, 120); + if((charCount + 43) >= MAX) + { + strncat(statMessage->messageContent, "\n", 2); + queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + bzero(statMessage, sizeof(userMessage)); + charCount = 0; + break; + } + else if(addNewline) + { + strncat(statMessage->messageContent, "|\n", 3); + charCount++; + addNewline = false; + } + else + { + addNewline = true; + } + currentSkill = currentSkill->next; + + } + queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + } + free(statMessage); + free(formattedString); + } + + // Spec command: Assign spec points to stats: + if(strncmp(currentCommand->command, "spec", 4) == 0) + { + userMessage * specMessage = calloc(1, sizeof(userMessage)); + specMessage->senderName[0] = '\0'; + char * formattedString = calloc(121, sizeof(char)); + if(currentCommand->caller->stats->specPoints > 0) + { + int selectedAmount = 0; + strtok(currentCommand->arguments, " "); + selectedAmount = atoi(¤tCommand->arguments[strlen(currentCommand->arguments) + 1]); + coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16); + if(selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) + { + switch (selectedStat) + { + case WITS: + { + currentCommand->caller->stats->wits += selectedAmount; + strncat(specMessage->messageContent, "Increased wits.", 16); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case INTELLECT: + { + currentCommand->caller->stats->intellect += selectedAmount; + strncat(specMessage->messageContent, "Increased intellect.", 21); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case STRENGTH: + { + currentCommand->caller->stats->strength += selectedAmount; + strncat(specMessage->messageContent, "Increased strength.", 20); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case ENDURANCE: + { + currentCommand->caller->stats->endurance += selectedAmount; + strncat(specMessage->messageContent, "Increased endurance.", 21); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case DEXERITY: + { + currentCommand->caller->stats->dexerity += selectedAmount; + strncat(specMessage->messageContent, "Increased dexerity.", 21); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case INVALID: + { + strncat(specMessage->messageContent, "Invalid stat.", 21); + } + } + } + else + { + strncat(specMessage->messageContent, "You have entered an invalid amount of spec points.", 51); + } + } + else + { + strncat(specMessage->messageContent, "You have no spec points available.", 35); + } + + // Send the message: + queueTargetedOutputMessage(parameters->outputQueue, specMessage, ¤tCommand->caller, 1); + + // Show the new stat sheet: + queue->lock = false; + queueCommand(queue, "stat", "", 5, 0, currentCommand->caller); + queue->lock = true; + + // Free the finished message: + free(specMessage); + free(formattedString); + } + if(strncmp(currentCommand->command, "skill", 5) == 0) + { + userMessage * skillMessage = calloc(1, sizeof(userMessage)); + skillMessage->senderName[0] = '\0'; + if((currentCommand->caller->stats->skillPoints - 1) >= 0) + { + int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments, + strlen(currentCommand->arguments), currentCommand->caller); + switch(returnValue) + { + case -1: + { + strcpy(skillMessage->messageContent, "Not a valid skill."); + break; + } + case 0: + { + strcpy(skillMessage->messageContent, "Took "); + strcat(skillMessage->messageContent, currentCommand->arguments); + strcat(skillMessage->messageContent, "."); + currentCommand->caller->stats->skillPoints--; + break; + } + } + } + else + { + strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n"); + } + queueTargetedOutputMessage(parameters->outputQueue, skillMessage, ¤tCommand->caller, 1); + free(skillMessage); + } + if(strncmp(currentCommand->command, "listskills", 10) == 0) + { + skillNode * currentSkill = parameters->globalSkillList->head; + userMessage * listMessage = calloc(1, sizeof(userMessage)); + char * formattedString = calloc(121, sizeof(char)); + int charCount = 0; + bool addNewline = false; + while(currentSkill != NULL) + { + snprintf(formattedString, 120, "| %-31s ", currentSkill->skill->skillName); + charCount += 43; + strncat(listMessage->messageContent, formattedString, 120); + if((charCount + 46) >= MAX) + { + queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); + bzero(listMessage, sizeof(userMessage)); + charCount = 0; + addNewline = false; + } + else if(addNewline) + { + strncat(listMessage->messageContent, "|\n", 3); + charCount++; + addNewline = false; + } + else + { + addNewline = true; + } + currentSkill = currentSkill->next; + } + queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); + free(listMessage); + free(formattedString); + } + // Remove the current command and unlock the queue: + currentCommand = NULL; + queue->lock = false; + dequeueCommand(queue); + return 0; +} + +// Run a stat check: +outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) +{ + if(chance > 100 || chance < 0) + { + return ERROR; + } + chance = 100 - chance; + int modifier = 0; + switch(statToCheck) + { + case WITS: + { + modifier = player->stats->wits * 4; + break; + } + case INTELLECT: + { + modifier = player->stats->intellect * 4; + break; + } + case STRENGTH: + { + modifier = player->stats->strength * 4; + break; + } + case ENDURANCE: + { + modifier = player->stats->endurance * 4; + break; + } + case DEXERITY: + { + modifier = player->stats->dexerity * 4; + break; + } + default: + { + return ERROR; + } + } + int attempt = (random() % 100) + modifier; + if(attempt >= chance) + { + if(attempt >= 98) + { + return CRITICAL_SUCCESS; + } + else + { + return SUCCESS; + } + } + else + { + if(attempt <= 2) + { + return CRITICAL_FAILURE; + } + else + { + return FAILURE; + } + } +} + diff --git a/src/gamelogic.h b/src/gamelogic.h new file mode 100644 index 0000000..6db3980 --- /dev/null +++ b/src/gamelogic.h @@ -0,0 +1,89 @@ +// 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 "lists.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; + areaNode * areaList; + playerInfo * connectedPlayers; + inputMessageQueue * inputQueue; + outputMessageQueue * outputQueue; + skillList * globalSkillList; +} gameLogicParameters; + +// Thread function which runs the main game loop, given the needed parameters: +void * gameLogicLoop(void * parameters); + +// ====================== +// -=[ Command Queue ]=-: +// ====================== +typedef struct commandEvent commandEvent; +typedef struct commandEvent +{ + playerInfo * caller; + commandEvent * next; + char * command; + char * arguments; +} commandEvent; + +// A first-in first-out queue for message input from players: +typedef struct commandQueue +{ + bool lock; + bool paused; + int currentLength; + commandEvent * back; + commandEvent * front; +} commandQueue; + +// Create a commandQueue: +commandQueue * createCommandQueue(void); + +// Enqueue a command to a commandQueue: +int queueCommand(commandQueue * queue, char * command, char * arguments, + int commandLength, int argumentsLength , playerInfo * callingPlayer); + +// Enqueue a messaged command to a commandQueue: +int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue); + +// Dequeue the front commandEvent from a commandQueue: +int dequeueCommand(commandQueue * queue); + +// Return the front commandEvent from a commandQueue: +commandEvent * peekCommand(commandQueue * queue); + +// Evaluate the next commandEvent: +int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); + +/* // Evaluate the given commandEvent: */ +/* int evaluateCommand(gameLogicParameters * parameters, commandEvent * command); */ + +// ============================ +// -=[ Gameplay Primitives ]=-: +// ============================ + +typedef enum outcome +{ + CRITICAL_FAILURE, + FAILURE, + SUCCESS, + CRITICAL_SUCCESS, + ERROR +} outcome; + +// Run a stat check: +outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); + +#endif diff --git a/src/inputoutput.c b/src/inputoutput.c index f2caa6a..24882b1 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -6,9 +6,9 @@ #include #include #include -#include "../include/constants.h" -#include "../include/playerdata.h" -#include "../include/inputoutput.h" +#include "constants.h" +#include "playerdata.h" +#include "inputoutput.h" // Sends a message to a given TLS session, wraps the calls to gnutls_write: int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) @@ -34,7 +34,7 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM do { returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, - sizeof(((userMessage*)0)->senderName)); + sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); do { @@ -313,7 +313,7 @@ int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, pla queue->back = inputMessage; queue->currentLength++; - // Unlock the queue: + // Unlock the queue: queue->lock = false; return 0; @@ -335,6 +335,19 @@ void userInputSanatize(char * inputString, int length) inputString[length - 1] = '\0'; } +void userNameSanatize(char * inputString, int length) +{ + for(int index = 0; index <= length; index++) + { + if(!isprint(inputString[index])) + { + inputString[index] = '\0'; + break; + } + } + inputString[length - 1] = '\0'; +} + // Return the front inputMessage from an inputMessageQueue: inputMessage * peekInputMessage(inputMessageQueue * queue) { diff --git a/include/inputoutput.h b/src/inputoutput.h similarity index 96% rename from include/inputoutput.h rename to src/inputoutput.h index 00a05a9..177fac0 100644 --- a/include/inputoutput.h +++ b/src/inputoutput.h @@ -12,7 +12,7 @@ // A message datastructure containing a user/character name and the content: typedef struct userMessage -{ +{ char senderName[32]; char messageContent[MAX]; } userMessage; @@ -100,5 +100,8 @@ inputMessage * peekInputMessage(inputMessageQueue * queue); // Sanatize user input to ensure it's okay to send to the server: void userInputSanatize(char * inputString, int length); +// Sanatize user names so they display correctly; +void userNameSanatize(char * inputString, int length); + #endif diff --git a/src/lists.c b/src/lists.c index a7379d0..e0c79b6 100644 --- a/src/lists.c +++ b/src/lists.c @@ -1,7 +1,7 @@ // Implementation of lists library for SilverMUD. // Barry Kane, 2021 -#include "../include/lists.h" -#include "../include/playerdata.h" +#include "lists.h" +#include "playerdata.h" areaNode * createAreaList(playerArea * initialArea) { @@ -33,7 +33,7 @@ int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) current->next = malloc(sizeof(areaNode)); current->next->prev = current; current->next->data = areaToAdd; - current->next->next = NULL; + current->next->next = NULL; } int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) @@ -61,7 +61,7 @@ int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) current = toList; while(current->next != NULL) { - current = current->next; + current = current->next; } current->next = malloc(sizeof(pathNode)); current->next->prev = current; diff --git a/include/lists.h b/src/lists.h similarity index 100% rename from include/lists.h rename to src/lists.h diff --git a/src/playerdata.c b/src/playerdata.c index b4f52c7..9d7ae58 100644 --- a/src/playerdata.c +++ b/src/playerdata.c @@ -1,18 +1,21 @@ // playerdata.c: Contains functions definitions for working with player data. // Barry Kane, 2021 +#include #include #include #include -#include "../include/playerdata.h" +#include +#include "constants.h" +#include "playerdata.h" // Move a player to a different area given a path in the area: -int movePlayerToArea(playerInfo * player, char * requestedPath) + int movePlayerToArea(playerInfo * player, char * requestedPath) { // Check if a number was given first: int selected = atoi(requestedPath); if(selected != 0) { - if(player->currentArea->areaExits[selected -1]->areaToJoin != NULL) + if(player->currentArea->areaExits[selected -1] != NULL && player->currentArea->areaExits[selected -1]->areaToJoin != NULL) { player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin; return 0; @@ -42,13 +45,24 @@ int movePlayerToArea(playerInfo * player, char * requestedPath) // Create an area given a name and description: playerArea * createArea(char * nameString, char * descriptionString) { + // Allocate and zero memory for the new area: playerArea * createdArea = calloc(1, sizeof(playerArea)); - strncpy(createdArea->areaName, nameString, 32); - strncpy(createdArea->areaDescription, descriptionString, 256); + + // Copy the strings into the newly created area: + strncpy(createdArea->areaName, nameString, 32 - 1); + strncpy(createdArea->areaDescription, descriptionString, MAX - 35); + + // Properly null-terminate the strings: + createdArea->areaName[31] = '\0'; + createdArea->areaDescription[MAX] = '\0'; + + // Ensure that all the paths are set to NULL: for(int index = 0; index < 16; index++) { createdArea->areaExits[index] = NULL; } + + // Return the pointer: return createdArea; } @@ -78,13 +92,301 @@ int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescriptio return 2; } } - playerPath * fromPath = calloc(1, sizeof(playerPath)); - playerPath * toPath = calloc(1, sizeof(playerPath)); + playerPath * fromPath = malloc(sizeof(playerPath)); + playerPath * toPath = malloc(sizeof(playerPath)); fromArea->areaExits[fromAreaSlot] = fromPath; toArea->areaExits[toAreaSlot] = toPath; - strncpy(fromArea->areaExits[fromAreaSlot]->pathName, fromDescription, 32); - strncpy(toArea->areaExits[toAreaSlot]->pathName, toDescription, 32); + strncpy(fromPath->pathName, fromDescription, 32 - 1); + fromPath->pathName[31] = '\0'; + strncpy(toPath->pathName, toDescription, 32 - 1); + toPath->pathName[31] = '\0'; fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea; toArea->areaExits[toAreaSlot]->areaToJoin = fromArea; + return 0; } +// Create a new skill and add it to the global skill list: +int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) +{ + if(skillNameLength >= 32) + { + fprintf(stderr, "Skill name is too long. Please shorten the name and try again.\n"); + return -1; + } + playerSkill * newSkill = malloc(sizeof(playerSkill)); + + strncpy(newSkill->skillName, skillName, 31); + newSkill->skillName[31] = '\0'; + + newSkill->skillPoints = 0; + newSkill->skillModifier = 0; + newSkill->trainedSkill = trainedSkill; + + // Add the skill to a node in the list: + return(addSkillNode(globalSkillList, newSkill)); +} + +// Add a skill node to a skill list: +int addSkillNode(skillList * skillList, playerSkill * skill) +{ + if(skillList->head == NULL) + { + skillList->head = malloc(sizeof(skillNode)); + skillList->head->skill = skill; + skillList->head->next = NULL; + skillList->skillCount = 1; + return 0; + } + else + { + skillNode * currentNode = skillList->head; + while(currentNode->next != NULL) + { + currentNode = currentNode->next; + } + currentNode->next = malloc(sizeof(skillNode)); + currentNode->next->skill = skill; + currentNode->next->next = NULL; + skillList->skillCount++; + return skillList->skillCount; + } +} + +// Remove a skill node from a skill list: +int removeSkillNode(skillList * skillList, playerSkill * skill) +{ + if(skillList->head->skill == skill) + { + free(skillList->head->skill); + if(skillList->head->next != NULL) + { + skillNode * deletedNode = skillList->head; + skillList->head = skillList->head->next; + free(deletedNode); + return 0; + } + else + { + skillNode * currentNode = skillList->head; + skillNode * previousNode = skillList->head; + while(currentNode->skill != skill) + { + if(currentNode->next == NULL) + { + return -1; + } + previousNode = currentNode; + currentNode = currentNode->next; + } + free(currentNode->skill); + previousNode->next = currentNode->next; + free(currentNode); + return 0; + } + } + return -1; +} + +// Take a skill and add it to the player's skill list: +int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer) +{ + + skillNode * currentNode = globalSkillList->head; + while(strncmp(skillName, currentNode->skill->skillName, 32) != 0) + { + if(currentNode->next == NULL) + { + fprintf(stderr, "Skill doesn't exist in skill list.\n"); + return -1; + } + currentNode = currentNode->next; + } + + bool playerHasSkill = false; + skillNode * currentPlayerNode = targetPlayer->skills->head; + while(currentPlayerNode != NULL) + { + if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0) + { + playerHasSkill = true; + break; + } + currentPlayerNode = currentPlayerNode->next; + } + if(playerHasSkill) + { + currentPlayerNode->skill->skillPoints++; + } + else + { + addSkillNode(targetPlayer->skills, currentNode->skill); + currentPlayerNode = targetPlayer->skills->head; + while(currentPlayerNode->next != NULL) + { + currentPlayerNode = currentPlayerNode->next; + } + currentPlayerNode->skill->skillPoints = 1; + } + return 0; +} + + +// Take a string containing a core stat name and return the core stat: +coreStat getCoreStatFromString(char * inputString, int stringLength) +{ + // Check we've got a long enough string to fit a stat: + if(stringLength < 4) + { + return INVALID; + } + + // Lowercase the string: + char * string = malloc(sizeof(char) * stringLength); + for(int index = 0; index < stringLength; index++) + { + string[index] = tolower(inputString[index]); + } + + // If we have a string that's at most just the stat name plus a null character, or + // a dirtier string, we can check in a better order and ignore impossibilites: + if(stringLength < 9) + { + if(stringLength <= 4) + { + if(strncmp(string, "wits", 4) == 0) + { + free(string); + return WITS; + } + else + { + free(string); + return INVALID; + } + } + // Hopefully one of the seven letter long ones: + else if(stringLength <= 7) + { + if(strncmp(string, "strength", 7) == 0) + { + free(string); + return STRENGTH; + } + else if(strncmp(string, "dexerity", 7) == 0) + { + free(string); + return DEXERITY; + } + if(strncmp(string, "wits", 4) == 0) + { + free(string); + return WITS; + } + else + { + free(string); + return INVALID; + } + } + // Hopefully one of the 8 letter long stats: + else + { + if(strncmp(string, "intellect", 8) == 0) + { + free(string); + return INTELLECT; + } + else if(strncmp(string, "endurance", 8) == 0) + { + free(string); + return ENDURANCE; + } + else if(strncmp(string, "strength", 7) == 0) + { + free(string); + return STRENGTH; + } + else if(strncmp(string, "dexerity", 7) == 0) + { + free(string); + return DEXERITY; + } + if(strncmp(string, "wits", 4) == 0) + { + free(string); + return WITS; + } + else + { + free(string); + return INVALID; + } + } + } + // Worst case, it's definitely a dirty string, compare them all: + else + { + if(strncmp(string, "wits", 4) == 0) + { + free(string); + return WITS; + } + else if(strncmp(string, "intellect", 8) == 0) + { + free(string); + return INTELLECT; + } + else if(strncmp(string, "strength", 7) == 0) + { + free(string); + return STRENGTH; + } + else if(strncmp(string, "endurance", 8) == 0) + { + free(string); + return ENDURANCE; + } + else if(strncmp(string, "dexerity", 7) == 0) + { + free(string); + return DEXERITY; + } + else + { + free(string); + return INVALID; + } + } +} + +int deallocatePlayer(playerInfo * playerToDeallocate) +{ + // Deallocate the skill list: + if(playerToDeallocate->skills->skillCount > 0) + { + // Allocate enough pointers: + skillNode * nodesToDeallocate[playerToDeallocate->skills->skillCount]; + skillNode * currentSkillNode = playerToDeallocate->skills->head; + + // Get a list of all the nodes together: + for(int index = 0; index < playerToDeallocate->skills->skillCount; index++) + { + nodesToDeallocate[index] = currentSkillNode; + currentSkillNode = currentSkillNode->next; + } + + // Deallocate all the nodes: + for(int index = 0; index < playerToDeallocate->skills->skillCount; index++) + { + free(nodesToDeallocate[index]); + } + } + + // Deallocate the stat block: + free(playerToDeallocate->stats); + + // Deallocate the player: + free(playerToDeallocate); + + return 0; +} diff --git a/src/playerdata.h b/src/playerdata.h new file mode 100644 index 0000000..db51b04 --- /dev/null +++ b/src/playerdata.h @@ -0,0 +1,114 @@ +// playerdata.h: Header file containing data structures for player data and function +// prototypes for interacting with said data. +#ifndef PLAYERDATA_H +#define PLAYERDATA_H +#include +#include +#include "constants.h" + +typedef struct playerPath playerPath; +typedef struct playerArea playerArea; + +struct playerPath +{ + char pathName[32]; + playerArea * areaToJoin; +}; + +struct playerArea +{ + char areaName[32]; + char areaDescription[MAX - 35]; + playerPath * areaExits[16]; +}; + +typedef struct statBlock +{ + // Levelling: + int level; + long experience; + + // Health: + int currentHealth; + int maxHealth; + + // Core Stats: + int wits; + int intellect; + int strength; + int endurance; + int dexerity; + + // Character Building: + int specPoints; + int skillPoints; +} statBlock; + +typedef struct playerSkill +{ + char skillName[32]; + int skillPoints; + int skillModifier; + bool trainedSkill; +} playerSkill; + +typedef struct skillNode skillNode; +struct skillNode +{ + playerSkill * skill; + skillNode * next; +}; + +typedef struct skillList +{ + skillNode * head; + int skillCount; +} skillList; + +typedef struct playerInfo +{ + char playerName[32]; + playerArea * currentArea; + statBlock * stats; + skillList * skills; +} playerInfo; + +typedef enum coreStat +{ + WITS, + INTELLECT, + STRENGTH, + ENDURANCE, + DEXERITY, + INVALID +} coreStat; + +// Move a player to a different area given a path in the area: +int movePlayerToArea(playerInfo * player, char * requestedPath); + +// Create an area given a name and description: +playerArea * createArea(char * nameString, char * descriptionString); + +// Create a path between two areas given two areas and two strings: +int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); + +// Create a new skill and add it to the global skill list: +int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill); + +// Add a skill node to a skill list: +int addSkillNode(skillList * skillList, playerSkill * skill); + +// Remove a skill node from a skill list: +int removeSkillNode(skillList * skillList, playerSkill * skill); +int removeSkillByID(skillList * skillList, playerSkill * skill); + +// Take a skill and add it to the player's skill list: +int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer); +int takeSkillbyID(skillList * globalSkillList, int skillID, playerInfo * targetPlayer); + +// Take a string containing a core stat name and return the core stat: +coreStat getCoreStatFromString(char * string, int stringLength); + +// Deallocate a player: +int deallocatePlayer(playerInfo * playerToDeallocate); +#endif diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index be97ab7..1e68c64 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -1,6 +1,7 @@ // Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.3. // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 +#include #include #include #include @@ -14,17 +15,19 @@ #include #include #include -#include "../../include/lists.h" -#include "../../include/gamelogic.h" -#include "../../include/constants.h" -#include "../../include/playerdata.h" -#include "../../include/texteffects.h" -#include "../../include/inputoutput.h" + +#include "../lists.h" +#include "../gamelogic.h" +#include "../constants.h" +#include "../playerdata.h" +#include "../texteffects.h" +#include "../inputoutput.h" typedef struct sockaddr sockaddr; int main() { + time_t currentTime; bool keepRunning = true; int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; @@ -38,25 +41,61 @@ int main() inputMessageQueue * inputQueue = createInputMessageQueue(); outputMessageQueue * outputQueue = createOutputMessageQueue(); + // -==[ TEST GAME-STATE INITIALIZATION ]==- // 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."); + areaNode * areas = createAreaList(createArea("Login Area", "Please login with the /join command.")); + addAreaNodeToList(areas, createArea("Temple Entrance", + "You are standing outside a large, elaborate temple, of white marble and delicate construction. " + "Etched onto the left pillar next to the large opening is the same symbol, over and over again, a gentle curve with it's ends pointing to the right. " + "A similar symbol is on the right pillar, but it's ends are pointing to the left. ")); + + addAreaNodeToList(areas, createArea("The Hall of Documentation", + "Just past the threshold of the entrance lies a large hall, with bookshelves lining the walls, ceiling to floor. " + "The shelves are filled to the brim with finely-bound books, each with titles in silver lettering on the spine. " + "There are countless books, but you notice a large lectern in the center of the room, and a path leading upwards at the back. ")); + + addAreaNodeToList(areas, createArea("Monument to GNU", + "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.")); + // Initialize test paths: + createPath(getAreaFromList(areas, 1), getAreaFromList(areas, 2), "Go inside the temple.", "Leave the temple."); + createPath(getAreaFromList(areas, 3), getAreaFromList(areas, 2), "Back to the Hall of Documentation.", "Path to Enlightenment."); + skillList * globalSkillList = malloc(sizeof(skillList)); + globalSkillList->head = NULL; + + // Create a few basic skills: + createSkill(globalSkillList, "Medicine", 8, true); + createSkill(globalSkillList, "Lockpicking", 12, true); + createSkill(globalSkillList, "Programming", 12, true); + createSkill(globalSkillList, "Sensor Reading", 14, false); + createSkill(globalSkillList, "Starship Piloting", 17, true); + createSkill(globalSkillList, "Mechanical Repair", 17, true); // Initialize playerdata: for (int index = 0; index < PLAYERCOUNT; index++) { sprintf(testString, "UNNAMED %d", index); + // OH NO IT'S NOT MEMORY SAFE BETTER REWRITE IT IN RUST + // But wait, we know the string won't be too big, so it's fine. strcpy(connectedPlayers[index].playerName, testString); connectedPlayers[index].currentArea = getAreaFromList(areas, 0); + connectedPlayers[index].stats = calloc(1, sizeof(statBlock)); + connectedPlayers[index].stats->specPoints = 30; + connectedPlayers[index].stats->skillPoints = 30; + connectedPlayers[index].skills = calloc(1, sizeof(skillList)); + connectedPlayers[index].skills->head = NULL; } + + // -==[ TEST GAME-STATE INITIALIZATION END ]==- // 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); - + + // Seed random number generator from the current time: + srandom((unsigned) time(¤tTime)); + // Initialize the sockets to 0, so we don't crash. for (int index = 0; index < PLAYERCOUNT; index++) { @@ -67,7 +106,7 @@ int main() socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); if (socketFileDesc == -1) { - perror("\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); exit(0); } @@ -75,7 +114,7 @@ int main() { slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000); } - + bzero(&serverAddress, sizeof(serverAddress)); // Assign IP and port: @@ -86,7 +125,7 @@ int main() // Binding newly created socket to given IP, and checking it works: if ((bind(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress))) != 0) { - perror("\tSocket Binding is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + fprintf(stderr, "\tSocket Binding is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); exit(0); } @@ -98,48 +137,53 @@ int main() // Let's start listening: if ((listen(socketFileDesc, PLAYERCOUNT)) != 0) { - perror("\tServer Listening is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + fprintf(stderr, "\tServer Listener is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); exit(EXIT_FAILURE); } else { - slowPrint("\tServer Listening is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", 5000); } length = sizeof(clientAddress); + // Declare the needed variables for TLS sessions: gnutls_session_t tlssessions[PLAYERCOUNT]; gnutls_anon_server_credentials_t serverkey = NULL; gnutls_anon_allocate_server_credentials(&serverkey); gnutls_anon_set_server_known_dh_params(serverkey, GNUTLS_SEC_PARAM_MEDIUM); - // Initialize all the TLS Sessions to NULL: We use this to check if it's an "empty connection." + // Initialize all the TLS sessions to NULL: We use this to check if it's an "empty connection." for (int index = 0; index < PLAYERCOUNT; index++) { tlssessions[index] = NULL; if (gnutls_init(&tlssessions[index], GNUTLS_SERVER) < 0) { - perror("\tTLS Sessions Initialization is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + fprintf(stderr, "\tTLS Sessions Initialization is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); exit(EXIT_FAILURE); } gnutls_priority_set_direct(tlssessions[index], "NORMAL:+ANON-ECDH:+ANON-DH", NULL); gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - } - slowPrint("\tTLS Sessions Initialization is:\t\033[32;40mGREEN.\033[0m\n", 5000); + } + slowPrint("\tTLS Preparation 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->globalSkillList = globalSkillList; gameLogicThreadParameters->outputQueue = outputQueue; gameLogicThreadParameters->inputQueue = inputQueue; + gameLogicThreadParameters->areaList = areas; pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters); + slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("=====\n", 5000); struct timeval timeout = {0, 500}; while(keepRunning) { - // Clear the set of file descriptors and add the master socket: + // Clear the set of file descriptors angad add the master socket: FD_ZERO(&connectedClients); FD_SET(socketFileDesc, &connectedClients); clientsAmount = socketFileDesc; @@ -168,7 +212,7 @@ int main() // Check if select() worked: if ((activityCheck < 0) && (errno != EINTR)) { - perror("Error in select(), retrying.\n"); + fprintf(stderr, "Error in select(), retrying.\n"); } // If it's the master socket selected, there is a new connection: @@ -176,7 +220,7 @@ int main() { if ((connectionFileDesc = accept(socketFileDesc, (struct sockaddr *)&clientAddress, (socklen_t*)&length)) < 0) { - perror("Failed to accept connection. Aborting.\n"); + fprintf(stderr, "Failed to accept connection. Aborting.\n"); exit(EXIT_FAILURE); } // See if we can put in the client: @@ -193,9 +237,13 @@ int main() returnVal = gnutls_handshake(tlssessions[index]); } while (returnVal < 0 && gnutls_error_is_fatal(returnVal) == 0); + + // Send a greeting message: strcpy(sendBuffer.senderName, ""); strcpy(sendBuffer.messageContent, "Welcome to the server!"); messageSend(tlssessions[index], &sendBuffer); + strcpy(receiveBuffer.messageContent, "/look"); + queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); break; } } @@ -210,45 +258,46 @@ int main() if(FD_ISSET(socketCheck, &connectedClients)) { int returnVal = messageReceive(tlssessions[index], &receiveBuffer); + // If player has disconnected: if(returnVal == -10 || returnVal == 0) { + // Close the session: gnutls_bye(tlssessions[index], GNUTLS_SHUT_WR); gnutls_deinit(tlssessions[index]); shutdown(clientSockets[index], 2); close(clientSockets[index]); clientSockets[index] = 0; tlssessions[index] = NULL; + + // Clear out the old player state so that a new one may join: + sprintf(testString, "UNNAMED %d", index); + strcpy(connectedPlayers[index].playerName, testString); + connectedPlayers[index].currentArea = getAreaFromList(areas, 0); + + // Prepare a fresh SSL session for the next new player: gnutls_init(&tlssessions[index], GNUTLS_SERVER); gnutls_priority_set_direct(tlssessions[index], "NORMAL:+ANON-ECDH:+ANON-DH", NULL); gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - } - else + } + // Otherwise, they've sent a message: + else { queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); } } } } - // 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); */ - /* } */ + // Run through the output queue and send all unused messages: while(outputQueue->currentLength != 0) { while(outputQueue->lock); outputQueue->lock = true; outputMessage * message = peekOutputMessage(outputQueue); outputQueue->lock = false; + + // If the first target is set to NULL, it's intended for all connected: if(message->targets[0] == NULL) { for (int index = 0; index < PLAYERCOUNT; index++) diff --git a/src/texteffects.c b/src/texteffects.c index 5920be3..11860ad 100644 --- a/src/texteffects.c +++ b/src/texteffects.c @@ -1,6 +1,7 @@ // texteffects.c: Implementation of text effect library for SilverMUD. // Barry Kane, 2021. #include +#include #include #include @@ -38,3 +39,60 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bol } wrefresh(window); } + +void bruteForcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) +{ + int characterIndex = 0; + if(bolded) + { + wattron(window, A_BOLD); + } + while(stringToPrint[characterIndex] != '\0') + { + for(char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++) + { + waddch(window, currentCharacter); + wrefresh(window); + usleep(delay); + waddch(window, 8); + wrefresh(window); + } + waddch(window, stringToPrint[characterIndex]); + characterIndex++; + } + if(bolded) + { + wattroff(window, A_BOLD); + } + wrefresh(window); +} + +void wrapString(char * stringToWrap, int stringLength, int screenWidth) +{ + int characterCount = 0; + for(int index = 0; index < stringLength; index++) + { + if(stringToWrap[index] == '\n') + { + characterCount = 0; + } + else + { + characterCount++; + } + if(characterCount == screenWidth) + { + while(!isspace(stringToWrap[index]) && index > 0) + { + index--; + } + if(index == 0) + { + return; + } + stringToWrap[index] = '\n'; + index++; + characterCount = 0; + } + } +} diff --git a/include/texteffects.h b/src/texteffects.h similarity index 95% rename from include/texteffects.h rename to src/texteffects.h index 38e5177..abcf5c7 100644 --- a/include/texteffects.h +++ b/src/texteffects.h @@ -14,4 +14,5 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bol // A string containing an ASCII art version of the Silverkin Industries logo. char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; +void wrapString(char * stringToWrap, int stringLength, int screenWidth); #endif diff --git a/tests/corestat-from-string.c b/tests/corestat-from-string.c new file mode 100644 index 0000000..ab70d82 --- /dev/null +++ b/tests/corestat-from-string.c @@ -0,0 +1,11 @@ +// Hopefully optimized corestat to string: +#include +#include +#include +#include +#include "../src/playerdata.h" + +void main(int argc, char ** argv) +{ + getCoreStatFromString(argv[1], strlen(argv[1])); +} diff --git a/tests/list-test.c b/tests/list-test.c index e522514..9fbeb6a 100644 --- a/tests/list-test.c +++ b/tests/list-test.c @@ -1,5 +1,5 @@ -#include "../misc/lists.h" -#include "../misc/playerdata.h" +#include "../src/lists.h" +#include "../src/playerdata.h" #include void main() diff --git a/tests/outputQueue-test.c b/tests/outputQueue-test.c index 9cc894d..a36fd43 100644 --- a/tests/outputQueue-test.c +++ b/tests/outputQueue-test.c @@ -1,4 +1,4 @@ -#include "../misc/inputoutput.h" +#include "../src/inputoutput.h" #include #include int main() From 60110d3abd48a413c904c559a39f1d13c705709b Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 16 Oct 2022 21:28:32 +0100 Subject: [PATCH 19/53] Made client exit gracefully upon server exit: - The client now checks the return value of messageReceive. - Renamed lists.c/.h to areadata.c/.h. --- src/{lists.c => areadata.c} | 6 +++--- src/{lists.h => areadata.h} | 8 ++++---- src/client/SilverMUDClient.c | 21 ++++++++++++++++----- src/gamelogic.h | 2 +- src/server/SilverMUDServer.c | 7 ++++--- 5 files changed, 28 insertions(+), 16 deletions(-) rename src/{lists.c => areadata.c} (95%) rename src/{lists.h => areadata.h} (86%) diff --git a/src/lists.c b/src/areadata.c similarity index 95% rename from src/lists.c rename to src/areadata.c index e0c79b6..80faa91 100644 --- a/src/lists.c +++ b/src/areadata.c @@ -1,6 +1,6 @@ -// Implementation of lists library for SilverMUD. -// Barry Kane, 2021 -#include "lists.h" +// areadata.c: Implements functions for playerAreas and playerPaths in SilverMUD: +// Barra Ó Catháin, 2022. +#include "areadata.h" #include "playerdata.h" areaNode * createAreaList(playerArea * initialArea) diff --git a/src/lists.h b/src/areadata.h similarity index 86% rename from src/lists.h rename to src/areadata.h index d667dc7..c3b136e 100644 --- a/src/lists.h +++ b/src/areadata.h @@ -1,7 +1,7 @@ -// lists.h: A header file for the lists library for SilverMUD. -// Barry Kane, 2021. -#ifndef LISTS_H -#define LISTS_H +// areadata.h: Contains data structures and functions for playerAreas and playerPaths in SilverMUD: +// Barra Ó Catháin, 2022. +#ifndef AREADATA_H +#define AREADATA_H #include "playerdata.h" typedef struct areaNode areaNode; diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index 57c57a1..444676b 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -13,6 +13,7 @@ #include #include #include + #include "../constants.h" #include "../playerdata.h" #include "../texteffects.h" @@ -66,21 +67,29 @@ void * messageSender(void * parameters) // Send the message off to the server: messageSend(threadParameters->tlsSession, &sendBuffer); } + + // Rejoin the main thread: pthread_exit(NULL); } void * messageReceiver(void * parameters) { - struct threadparameters *threadParameters = parameters; - bool serverMessage = false; + int returnValue = 0; userMessage receiveBuffer; + bool serverMessage = false; + struct threadparameters *threadParameters = parameters; int screenWidth = getmaxx(threadParameters->window); // Repeatedly take messages from the server and print them to the chat log window: while (!shouldExit) { - messageReceive(threadParameters->tlsSession, &receiveBuffer); - if (receiveBuffer.senderName[0] == '\0') + returnValue = messageReceive(threadParameters->tlsSession, &receiveBuffer); + // Check we haven't been disconnected: + if(returnValue == -10 || returnValue == 0) + { + shouldExit = true; + } + else if (receiveBuffer.senderName[0] == '\0') { wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth); @@ -123,7 +132,7 @@ void * messageReceiver(void * parameters) pthread_exit(NULL); } -int main(int argc, char **argv) +int main(int argc, char ** argv) { int socketFileDesc; struct sockaddr_in serverAddress; @@ -276,6 +285,8 @@ int main(int argc, char **argv) messageArea->window = newwin(3, COLS - 2, LINES - 4, 1); messageArea->tlsSession = tlsSession; messageArea->loggingFlag = gameLogging; + + // Set the appropriate log pointers: if (gameLog != NULL) { messageArea->loggingStream = gameLog; diff --git a/src/gamelogic.h b/src/gamelogic.h index 6db3980..b3a9f1b 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -3,7 +3,7 @@ // Barry Kane, 2022. #ifndef GAMELOGIC_H #define GAMELOGIC_H -#include "lists.h" +#include "areadata.h" #include "constants.h" #include "playerdata.h" #include "inputoutput.h" diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 1e68c64..f11772b 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -16,7 +16,7 @@ #include #include -#include "../lists.h" +#include "../areadata.h" #include "../gamelogic.h" #include "../constants.h" #include "../playerdata.h" @@ -94,7 +94,7 @@ int main() slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.3\n", 5000); // Seed random number generator from the current time: - srandom((unsigned) time(¤tTime)); + srandom((unsigned)time(¤tTime)); // Initialize the sockets to 0, so we don't crash. for (int index = 0; index < PLAYERCOUNT; index++) @@ -114,7 +114,8 @@ int main() { slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000); } - + + // bzero(&serverAddress, sizeof(serverAddress)); // Assign IP and port: From 52b4b1e2f05ba27a0576e05fd3e4d7fa0b8b744b Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Tue, 18 Oct 2022 21:00:57 +0100 Subject: [PATCH 20/53] Warning fixes and Makefile edits - Adjusted the Makefile to create gprof data in the server (currently inactive as the server never terminates.) - Fixed warnings in areadata.c and gamelogic.c. - Added bruteforcePrint for completeness. --- Makefile | 14 +++++++------- src/areadata.c | 4 ++++ src/gamelogic.h | 3 +++ src/texteffects.c | 20 +++++++++++++++++++- src/texteffects.h | 24 +++++++++++++++++++++--- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 01d6e91..926ebf0 100644 --- a/Makefile +++ b/Makefile @@ -8,22 +8,22 @@ serverobj = $(serversrc:.c=.o) CLIENTLDFLAGS= -lpthread -lncurses -lgnutls SERVERLDFLAGS= -lpthread -lncurses -lgnutls SilverMUDClient: $(clientobj) - gcc -s -O3 -o $@ $^ $(CLIENTLDFLAGS) + gcc -o $@ $^ $(CLIENTLDFLAGS) SilverMUDServer: $(serverobj) - gcc -s -O3 -o $@ $^ $(SERVERLDFLAGS) + gcc -o $@ $^ $(SERVERLDFLAGS) SilverMUDClientDebug: $(clientobj) - gcc -ggdb -Wall $^ $(CLIENTLDFLAGS) -o $@ + gcc $^ $(CLIENTLDFLAGS) -o $@ SilverMUDServerDebug: $(serverobj) - gcc -ggdb -Wall $^ $(SERVERLDFLAGS) -o $@ + gcc $^ $(SERVERLDFLAGS) -o $@ .PHONY: clean clean: rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug all: SilverMUDClient SilverMUDServer - -debug: CFLAGS += -Wall -ggdb -Wextra -debug: SilverMUDClientDebug SilverMUDServerDebug +all: CFLAGS += -Wall -Wextra -Ofast +debug: CFLAGS += -Wall -ggdb -Wextra -Og -pg +debug: clean SilverMUDClientDebug SilverMUDServerDebug diff --git a/src/areadata.c b/src/areadata.c index 80faa91..5d15af0 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -34,6 +34,7 @@ int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) current->next->prev = current; current->next->data = areaToAdd; current->next->next = NULL; + return 0; } int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) @@ -53,6 +54,7 @@ int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) current->next->prev = current->prev; } free(current); + return 0; } int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) @@ -67,6 +69,7 @@ int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) current->next->prev = current; current->next->data = pathToAdd; current->next->next = NULL; + return 0; } int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) @@ -86,6 +89,7 @@ int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) current->next->prev = current->prev; } free(current); + return 0; } areaNode * getAreaNode(areaNode * fromList, int listIndex) diff --git a/src/gamelogic.h b/src/gamelogic.h index b3a9f1b..d77085f 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -86,4 +86,7 @@ typedef enum outcome // Run a stat check: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); +// Run a skill check: +// outcome skillCheck(playerInfo * player, int chance, char * skillToCheck, int skillNameLength); + #endif diff --git a/src/texteffects.c b/src/texteffects.c index 11860ad..34328fc 100644 --- a/src/texteffects.c +++ b/src/texteffects.c @@ -40,7 +40,25 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bol wrefresh(window); } -void bruteForcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) +void bruteforcePrint(char * stringToPrint, int delay) +{ + unsigned int characterIndex = 0; + while(stringToPrint[characterIndex] != '\0') + { + for(unsigned char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++) + { + putchar(stringToPrint[currentCharacter]); + fflush(stdout); + usleep(delay); + putchar(8); + fflush(stdout); + } + putchar(stringToPrint[characterIndex]); + characterIndex++; + } +} + +void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) { int characterIndex = 0; if(bolded) diff --git a/src/texteffects.h b/src/texteffects.h index abcf5c7..3531e77 100644 --- a/src/texteffects.h +++ b/src/texteffects.h @@ -5,14 +5,32 @@ #include #include -// A fancy, character by character print. Similar to a serial terminal with lower baud rate. +// A character by character print, similar to a serial terminal with lower baud rate. void slowPrint(char * stringToPrint, int delay); -// The same, altered to work with Ncurses. +// The same, altered to work with ncurses. void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); +// A character by character "brute-force" print, similar to Hollywood hacking scenes. +void bruteforcePrint(char * stringToPrint, int delay); + +// The same, altered to work with ncurses. +void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); + // A string containing an ASCII art version of the Silverkin Industries logo. -char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; +char * logostring = + " ///////\n" + " //////////////////////////////////////////\n" + " ///////////////////////////////////////////////////////////\n" + " ////////// ////////////////////////////\n" + " ### # # # # ##### ### # # # # # /////////////////\n" + " ## # # # # ## # # ### # ## # //////////////\n" + " ## # # # # # ### # # # # # # /////////\n" + " ### # ### # ##### # # # # # # # ///////\n" + " # ## # ##### # # ### ### ### # ##### ### ////// \n" + " # # # # # # # # ## # # # # ## ## ////\n" + " # # # # # # # # ## # ### # # ## //\n" + " # # ### ##### ##### ### # # # # #### ### /\n"; void wrapString(char * stringToWrap, int stringLength, int screenWidth); #endif From f2dd83857ffc6c60cd40872910fe73057202d492 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 23 Oct 2022 17:07:13 +0100 Subject: [PATCH 21/53] Completed Reorganization of Area Data - Moved the appropriate data structures and functions into areaData. - Made movePlayerToArea a gameplay primitive. --- src/areadata.c | 93 ++++++++++++++++++++++++++++++++++++++++++--- src/areadata.h | 40 ++++++++++++++++++- src/gamelogic.c | 34 +++++++++++++++++ src/gamelogic.h | 3 ++ src/inputoutput.c | 9 ++--- src/playerdata.c | 97 ----------------------------------------------- src/playerdata.h | 27 +------------ 7 files changed, 169 insertions(+), 134 deletions(-) diff --git a/src/areadata.c b/src/areadata.c index 5d15af0..cdab489 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -1,8 +1,81 @@ // areadata.c: Implements functions for playerAreas and playerPaths in SilverMUD: // Barra Ó Catháin, 2022. +#include #include "areadata.h" #include "playerdata.h" +// ==================== +// -=[ Area/Paths: ]=-: +// ==================== + +// Create an area given a name and description: +playerArea * createArea(char * nameString, char * descriptionString) +{ + // Allocate and zero memory for the new area: + playerArea * createdArea = calloc(1, sizeof(playerArea)); + + // Copy the strings into the newly created area: + strncpy(createdArea->areaName, nameString, 32 - 1); + strncpy(createdArea->areaDescription, descriptionString, MAX - 35); + + // Properly null-terminate the strings: + createdArea->areaName[31] = '\0'; + createdArea->areaDescription[MAX] = '\0'; + + // Ensure that all the paths are set to NULL: + for(int index = 0; index < 16; index++) + { + createdArea->areaExits[index] = NULL; + } + + // Return the pointer: + return createdArea; +} + +// Create a path between two areas given two areas and two strings: +int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription) +{ + int fromAreaSlot, toAreaSlot; + for(fromAreaSlot = 0; fromAreaSlot < 16; fromAreaSlot++) + { + if(fromArea->areaExits[fromAreaSlot] == NULL) + { + break; + } + if((fromArea->areaExits[fromAreaSlot] != NULL) && (fromAreaSlot == 15)) + { + return 1; + } + } + for(toAreaSlot = 0; toAreaSlot < 32; toAreaSlot++) + { + if(toArea->areaExits[toAreaSlot] == 0) + { + break; + } + if((toArea->areaExits[toAreaSlot] != 0) && (toAreaSlot == 31)) + { + return 2; + } + } + playerPath * fromPath = malloc(sizeof(playerPath)); + playerPath * toPath = malloc(sizeof(playerPath)); + fromArea->areaExits[fromAreaSlot] = fromPath; + toArea->areaExits[toAreaSlot] = toPath; + strncpy(fromPath->pathName, fromDescription, 32 - 1); + fromPath->pathName[31] = '\0'; + strncpy(toPath->pathName, toDescription, 32 - 1); + toPath->pathName[31] = '\0'; + fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea; + toArea->areaExits[toAreaSlot]->areaToJoin = fromArea; + return 0; +} + +// ========================= +// -=[ Area/Path Lists: ]=-: +// ========================= + +// Create and initialize an areaList: areaNode * createAreaList(playerArea * initialArea) { areaNode * newAreaList = malloc(sizeof(areaNode)); @@ -12,7 +85,7 @@ areaNode * createAreaList(playerArea * initialArea) return newAreaList; } - +// Create and initialize an pathList: pathNode * createPathList(playerPath * initialPath) { pathNode * newPathList = malloc(sizeof(pathNode)); @@ -22,13 +95,16 @@ pathNode * createPathList(playerPath * initialPath) return newPathList; } +// Adds an areaNode to the end of a list, returning it's position: int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) { areaNode * current; + int index = 0; current = toList; while(current->next != NULL) { current = current->next; + index++; } current->next = malloc(sizeof(areaNode)); current->next->prev = current; @@ -37,6 +113,7 @@ int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) return 0; } +// Removes an areaNode from the list, returning 0 on success and -1 on failure: int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) { areaNode * current = fromList; @@ -46,7 +123,7 @@ int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) } if(current->next == NULL && current->data != areaToDelete) { - return 0; + return -1; } current->prev->next = current->next; if(current->next != NULL) @@ -57,21 +134,25 @@ int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) return 0; } +// Adds an pathNode to the end of a list, returning it's position: int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) { pathNode * current; + int index = 0; current = toList; while(current->next != NULL) { current = current->next; + index++; } current->next = malloc(sizeof(pathNode)); current->next->prev = current; current->next->data = pathToAdd; current->next->next = NULL; - return 0; + return index; } +// Removes an pathNode from the list, returning 0 on success and -1 on failure: int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) { pathNode * current = fromList; @@ -81,7 +162,7 @@ int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) } if(current->next == NULL && current->data != pathToDelete) { - return 0; + return -1; } current->prev->next = current->next; if(current->next != NULL) @@ -92,6 +173,7 @@ int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) return 0; } +// Return the areaNode at the given index from the list: areaNode * getAreaNode(areaNode * fromList, int listIndex) { areaNode * current = fromList; @@ -109,6 +191,7 @@ areaNode * getAreaNode(areaNode * fromList, int listIndex) return current; } +// Return the pathNode at the given index from the list: pathNode * getPathNode(pathNode * fromList, int listIndex) { pathNode * current = fromList; @@ -126,9 +209,9 @@ pathNode * getPathNode(pathNode * fromList, int listIndex) return current; } +// Return the playerArea of the areaNode at the given index from the list: playerArea * getAreaFromList(areaNode * fromList, int listIndex) { areaNode * current = getAreaNode(fromList, listIndex); return current->data; } - diff --git a/src/areadata.h b/src/areadata.h index c3b136e..53da6e6 100644 --- a/src/areadata.h +++ b/src/areadata.h @@ -2,7 +2,36 @@ // Barra Ó Catháin, 2022. #ifndef AREADATA_H #define AREADATA_H -#include "playerdata.h" +#include "constants.h" +// ==================== +// -=[ Area/Paths: ]=-: +// ==================== + +typedef struct playerPath playerPath; +typedef struct playerArea playerArea; + +struct playerPath +{ + char pathName[32]; + playerArea * areaToJoin; +}; + +struct playerArea +{ + char areaName[32]; + char areaDescription[MAX - 35]; + playerPath * areaExits[16]; +}; + +// Create an area given a name and description: +playerArea * createArea(char * nameString, char * descriptionString); + +// Create a path between two areas given two areas and two strings: +int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); + +// ========================= +// -=[ Area/Path Lists: ]=-: +// ========================= typedef struct areaNode areaNode; typedef struct pathNode pathNode; @@ -21,22 +50,31 @@ struct areaNode areaNode * prev; }; +// Create and initialize an areaList: areaNode * createAreaList(playerArea * initialArea); +// Create and initialize an pathList: pathNode * createPathList(playerPath * initialPath); +// Adds an areaNode to the end of a list, returning it's position: int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd); +// Removes an areaNode from the list, returning 0 on success and -1 on failure: int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete); +// Adds an pathNode to the end of a list, returning it's position: int addPathNodeToList(pathNode * toList, playerPath * pathToAdd); +// Removes an pathNode from the list, returning 0 on success and -1 on failure: int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete); +// Return the areaNode at the given index from the list: areaNode * getAreaNode(areaNode * fromList, int listIndex); +// Return the pathNode at the given index from the list: pathNode * getPathNode(pathNode * fromList, int listIndex); +// Return the playerArea of the areaNode at the given index from the list: playerArea * getAreaFromList(areaNode * fromList, int listIndex); // TO BE IMPLEMENTED: diff --git a/src/gamelogic.c b/src/gamelogic.c index 3161d86..97041a9 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -708,3 +708,37 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } } +// Move a player to a different area given a path in the area: +int movePlayerToArea(playerInfo * player, char * requestedPath) +{ + // Check if a number was given first: + int selected = atoi(requestedPath); + if(selected != 0) + { + if(player->currentArea->areaExits[selected - 1] != NULL && + player->currentArea->areaExits[selected - 1]->areaToJoin != NULL) + { + player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin; + return 0; + } + else + { + return 1; + } + } + + // Otherwise search for the description: + for (int index = 0; index < 16; index++) + { + if(player->currentArea->areaExits[index] != NULL) + { + if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) + { + printf("%s: %s\n", player->playerName, player->currentArea->areaExits[index]->pathName); + player->currentArea = player->currentArea->areaExits[index]->areaToJoin; + return 0; + } + } + } + return 1; +} diff --git a/src/gamelogic.h b/src/gamelogic.h index d77085f..b73d2a4 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -74,6 +74,9 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); // -=[ Gameplay Primitives ]=-: // ============================ +// Player movement: +int movePlayerToArea(playerInfo * player, char * requestedPath); + typedef enum outcome { CRITICAL_FAILURE, diff --git a/src/inputoutput.c b/src/inputoutput.c index 24882b1..e6af436 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -24,6 +24,7 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, sizeof(((userMessage*)0)->messageContent)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + return returnValue; } @@ -41,6 +42,7 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, sizeof(((userMessage*)0)->messageContent)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + return returnValue; } @@ -61,10 +63,6 @@ int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) // Allocate the internal userMessage to store the message: 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(newOutputMessage->content->senderName, messageToQueue.senderName, 32); @@ -134,8 +132,7 @@ int queueTargetedOutputMessage(outputMessageQueue * queue, // 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); diff --git a/src/playerdata.c b/src/playerdata.c index 9d7ae58..50f32e0 100644 --- a/src/playerdata.c +++ b/src/playerdata.c @@ -8,103 +8,6 @@ #include "constants.h" #include "playerdata.h" -// Move a player to a different area given a path in the area: - int movePlayerToArea(playerInfo * player, char * requestedPath) -{ - // Check if a number was given first: - int selected = atoi(requestedPath); - if(selected != 0) - { - if(player->currentArea->areaExits[selected -1] != NULL && player->currentArea->areaExits[selected -1]->areaToJoin != NULL) - { - player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin; - return 0; - } - else - { - return 1; - } - } - - // Otherwise search for the description: - for (int index = 0; index < 16; index++) - { - if(player->currentArea->areaExits[index] != NULL) - { - if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) - { - printf("%s: %s\n", player->playerName, player->currentArea->areaExits[index]->pathName); - player->currentArea = player->currentArea->areaExits[index]->areaToJoin; - return 0; - } - } - } - return 1; -} - -// Create an area given a name and description: -playerArea * createArea(char * nameString, char * descriptionString) -{ - // Allocate and zero memory for the new area: - playerArea * createdArea = calloc(1, sizeof(playerArea)); - - // Copy the strings into the newly created area: - strncpy(createdArea->areaName, nameString, 32 - 1); - strncpy(createdArea->areaDescription, descriptionString, MAX - 35); - - // Properly null-terminate the strings: - createdArea->areaName[31] = '\0'; - createdArea->areaDescription[MAX] = '\0'; - - // Ensure that all the paths are set to NULL: - for(int index = 0; index < 16; index++) - { - createdArea->areaExits[index] = NULL; - } - - // Return the pointer: - return createdArea; -} - -// Create a path between two areas given two areas and two strings: -int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription) -{ - int fromAreaSlot, toAreaSlot; - for(fromAreaSlot = 0; fromAreaSlot < 16; fromAreaSlot++) - { - if(fromArea->areaExits[fromAreaSlot] == NULL) - { - break; - } - if((fromArea->areaExits[fromAreaSlot] != NULL) && (fromAreaSlot == 15)) - { - return 1; - } - } - for(toAreaSlot = 0; toAreaSlot < 32; toAreaSlot++) - { - if(toArea->areaExits[toAreaSlot] == 0) - { - break; - } - if((toArea->areaExits[toAreaSlot] != 0) && (toAreaSlot == 31)) - { - return 2; - } - } - playerPath * fromPath = malloc(sizeof(playerPath)); - playerPath * toPath = malloc(sizeof(playerPath)); - fromArea->areaExits[fromAreaSlot] = fromPath; - toArea->areaExits[toAreaSlot] = toPath; - strncpy(fromPath->pathName, fromDescription, 32 - 1); - fromPath->pathName[31] = '\0'; - strncpy(toPath->pathName, toDescription, 32 - 1); - toPath->pathName[31] = '\0'; - fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea; - toArea->areaExits[toAreaSlot]->areaToJoin = fromArea; - return 0; -} - // Create a new skill and add it to the global skill list: int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) { diff --git a/src/playerdata.h b/src/playerdata.h index db51b04..9f78742 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -4,24 +4,9 @@ #define PLAYERDATA_H #include #include +#include "areadata.h" #include "constants.h" -typedef struct playerPath playerPath; -typedef struct playerArea playerArea; - -struct playerPath -{ - char pathName[32]; - playerArea * areaToJoin; -}; - -struct playerArea -{ - char areaName[32]; - char areaDescription[MAX - 35]; - playerPath * areaExits[16]; -}; - typedef struct statBlock { // Levelling: @@ -83,15 +68,6 @@ typedef enum coreStat INVALID } coreStat; -// Move a player to a different area given a path in the area: -int movePlayerToArea(playerInfo * player, char * requestedPath); - -// Create an area given a name and description: -playerArea * createArea(char * nameString, char * descriptionString); - -// Create a path between two areas given two areas and two strings: -int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); - // Create a new skill and add it to the global skill list: int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill); @@ -111,4 +87,5 @@ coreStat getCoreStatFromString(char * string, int stringLength); // Deallocate a player: int deallocatePlayer(playerInfo * playerToDeallocate); + #endif From d9497679cb8a3bd906400f13cd08bdb54876dee1 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 30 Oct 2022 12:58:39 +0000 Subject: [PATCH 22/53] Implemented Skill Checks. - Implemented skillCheck. - Allowed for the in-game testing of skillCheck via /try. - Slightly reorganized the Makefile. - Tweaked the logoString to display correctly. - Edited the client and server to generate gprof data when in debug builds. --- Makefile | 8 ++-- src/gamelogic.c | 93 ++++++++++++++++++++++++++++++++++-- src/gamelogic.h | 2 +- src/playerdata.h | 2 +- src/server/SilverMUDServer.c | 20 +++++--- src/texteffects.h | 22 ++++----- 6 files changed, 120 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index 926ebf0..90805f5 100644 --- a/Makefile +++ b/Makefile @@ -14,16 +14,16 @@ SilverMUDServer: $(serverobj) gcc -o $@ $^ $(SERVERLDFLAGS) SilverMUDClientDebug: $(clientobj) - gcc $^ $(CLIENTLDFLAGS) -o $@ + gcc -pg $^ $(CLIENTLDFLAGS) -o $@ SilverMUDServerDebug: $(serverobj) - gcc $^ $(SERVERLDFLAGS) -o $@ + gcc -pg $^ $(SERVERLDFLAGS) -o $@ .PHONY: clean clean: rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug -all: SilverMUDClient SilverMUDServer +all: clean SilverMUDClient SilverMUDServer all: CFLAGS += -Wall -Wextra -Ofast -debug: CFLAGS += -Wall -ggdb -Wextra -Og -pg +debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og debug: clean SilverMUDClientDebug SilverMUDServerDebug diff --git a/src/gamelogic.c b/src/gamelogic.c index 97041a9..112291f 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "constants.h" #include "gamelogic.h" #include "playerdata.h" @@ -18,9 +19,8 @@ void * gameLogicLoop(void * parameters) { gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; - bool keepRunning = true; commandQueue * commandQueue = createCommandQueue(); - while(keepRunning) + while(true) { // Evaluate remaining commands: if(commandQueue->currentLength != 0) @@ -74,7 +74,7 @@ void * gameLogicLoop(void * parameters) dequeueInputMessage(threadParameters->inputQueue); } } - return NULL; + pthread_exit(NULL); } // Create a commandQueue: @@ -311,7 +311,9 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) } default: { - strcpy(tryMessage->messageContent, "Not at the moment, mate.\n"); + sprintf(tryMessage->messageContent,"%d", + skillCheck(currentCommand->caller, 10, currentCommand->arguments, strlen(currentCommand->arguments), + parameters->globalSkillList)); break; } } @@ -645,11 +647,14 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) // Run a stat check: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) { + // Calculate the chance: if(chance > 100 || chance < 0) { return ERROR; } chance = 100 - chance; + + // Calculate the modifier: int modifier = 0; switch(statToCheck) { @@ -708,6 +713,86 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } } +// Run a skill check: +outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList) +{ + // Calculate the chance: + if(chance > 100 || chance < 0) + { + return ERROR; + } + chance = 100 - chance; + + // Check if the player has the given skill: + bool playerHasSkill = false; + skillNode * currentPlayerNode = player->skills->head; + while(currentPlayerNode != NULL) + { + if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0) + { + playerHasSkill = true; + break; + } + currentPlayerNode = currentPlayerNode->next; + } + + // If the player doesn't have the skill, check if it's in the game and is trained: + bool trainedSkill = false; + if(!playerHasSkill) + { + skillNode * currentNode = globalSkillList->head; + while(strncmp(skillName, currentNode->skill->skillName, 32) != 0) + { + if(currentNode->next == NULL) + { + fprintf(stderr, "Skill doesn't exist in skill list.\n"); + return ERROR; + } + currentNode = currentNode->next; + } + if(currentNode->skill->trainedSkill == true) + { + trainedSkill = true; + } + } + + // Calculate the modifier: + int modifier = 0; + if(trainedSkill) + { + modifier = -100; + } + else + { + modifier = currentPlayerNode->skill->skillPoints * 4; + } + + // Attempt the check: + int attempt = (random() % 100) + modifier; + if(attempt >= chance) + { + if(attempt >= 98) + { + return CRITICAL_SUCCESS; + } + else + { + return SUCCESS; + } + } + else + { + if(attempt <= 2) + { + return CRITICAL_FAILURE; + } + else + { + return FAILURE; + } + } +} + // Move a player to a different area given a path in the area: int movePlayerToArea(playerInfo * player, char * requestedPath) { diff --git a/src/gamelogic.h b/src/gamelogic.h index b73d2a4..7660063 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -90,6 +90,6 @@ typedef enum outcome outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); // Run a skill check: -// outcome skillCheck(playerInfo * player, int chance, char * skillToCheck, int skillNameLength); +outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList); #endif diff --git a/src/playerdata.h b/src/playerdata.h index 9f78742..01564b2 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -49,7 +49,7 @@ typedef struct skillList skillNode * head; int skillCount; } skillList; - +\ typedef struct playerInfo { char playerName[32]; diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index f11772b..7ba6083 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -24,11 +25,14 @@ #include "../inputoutput.h" typedef struct sockaddr sockaddr; - -int main() +void sigintHandler(int signal) +{ + exit(EXIT_SUCCESS); +} + +int main(int argc, char ** argv) { time_t currentTime; - bool keepRunning = true; int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; @@ -40,7 +44,10 @@ int main() struct sockaddr_in serverAddress, clientAddress; inputMessageQueue * inputQueue = createInputMessageQueue(); outputMessageQueue * outputQueue = createOutputMessageQueue(); - + + // Set the handler for SIGINT: + signal(2, sigintHandler); + // -==[ TEST GAME-STATE INITIALIZATION ]==- // Initialize test areas: areaNode * areas = createAreaList(createArea("Login Area", "Please login with the /join command.")); @@ -182,7 +189,7 @@ int main() slowPrint("=====\n", 5000); struct timeval timeout = {0, 500}; - while(keepRunning) + while(true) { // Clear the set of file descriptors angad add the master socket: FD_ZERO(&connectedClients); @@ -325,6 +332,7 @@ int main() dequeueOutputMessage(outputQueue); } } - return 0; + pthread_cancel(gameLogicThread); + exit(EXIT_SUCCESS); } diff --git a/src/texteffects.h b/src/texteffects.h index 3531e77..060c99c 100644 --- a/src/texteffects.h +++ b/src/texteffects.h @@ -20,17 +20,17 @@ void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bo // A string containing an ASCII art version of the Silverkin Industries logo. char * logostring = " ///////\n" - " //////////////////////////////////////////\n" - " ///////////////////////////////////////////////////////////\n" - " ////////// ////////////////////////////\n" - " ### # # # # ##### ### # # # # # /////////////////\n" - " ## # # # # ## # # ### # ## # //////////////\n" - " ## # # # # # ### # # # # # # /////////\n" - " ### # ### # ##### # # # # # # # ///////\n" - " # ## # ##### # # ### ### ### # ##### ### ////// \n" - " # # # # # # # # ## # # # # ## ## ////\n" - " # # # # # # # # ## # ### # # ## //\n" - " # # ### ##### ##### ### # # # # #### ### /\n"; + " //////////////////////////////////////////\n" + " ///////////////////////////////////////////////////////////\n" + " ////////// ////////////////////////////\n" + " ### # # # # ##### ### # # # # # /////////////////\n" + " ## # # # # ## # # ### # ## # //////////////\n" + " ## # # # # # ### # # # # # # /////////\n" + " ### # ### # ##### # # # # # # # ///////\n" + " # ## # ##### # # ### ### ### # ##### ### //////\n" + " # # # # # # # # ## # # # # ## ## ////\n" + " # # # # # # # # ## # ### # # ## //\n" + " # # ### ##### ##### ### # # # # #### ### /\n"; void wrapString(char * stringToWrap, int stringLength, int screenWidth); #endif From ca8ba5e41043aaea9823b4423ea1b69f30d7664a Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 30 Oct 2022 13:00:18 +0000 Subject: [PATCH 23/53] Incremented version numbering in preperation for merge to master. - Incremented version numbering to Alpha 0.4. --- src/client/SilverMUDClient.c | 4 ++-- src/server/SilverMUDServer.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index 444676b..b0a8c50 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.3. +// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.4. // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -148,7 +148,7 @@ int main(int argc, char ** argv) bool chatLogging = false, gameLogging = false; // Print welcome message: - slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.3\n", 5000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.4\n", 5000); // Parse command-line options: while ((currentopt = getopt(argc, argv, "i:c:g:p:d:")) != -1) diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 7ba6083..824de8d 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.3. +// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.4. // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -98,7 +98,7 @@ int main(int argc, char ** argv) // 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); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.4\n", 5000); // Seed random number generator from the current time: srandom((unsigned)time(¤tTime)); From c68e66e7bc92bf6fb349ac09adcadfdff29960db Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Mon, 31 Oct 2022 01:55:44 +0000 Subject: [PATCH 24/53] Squashed current warnings. - Fixed all current warnings. - Added the ability to set the text delay as a command line option in the server. --- src/areadata.c | 4 +-- src/client/SilverMUDClient.c | 6 ++-- src/gamelogic.c | 11 +++++--- src/inputoutput.c | 18 ++++++------ src/playerdata.c | 55 +++++++++++++++++++----------------- src/server/SilverMUDServer.c | 32 +++++++++++++++------ 6 files changed, 74 insertions(+), 52 deletions(-) diff --git a/src/areadata.c b/src/areadata.c index cdab489..060781d 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -16,11 +16,11 @@ playerArea * createArea(char * nameString, char * descriptionString) // Copy the strings into the newly created area: strncpy(createdArea->areaName, nameString, 32 - 1); - strncpy(createdArea->areaDescription, descriptionString, MAX - 35); + strncpy(createdArea->areaDescription, descriptionString, MAX - 36); // Properly null-terminate the strings: createdArea->areaName[31] = '\0'; - createdArea->areaDescription[MAX] = '\0'; + createdArea->areaDescription[MAX - 36] = '\0'; // Ensure that all the paths are set to NULL: for(int index = 0; index < 16; index++) diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index b0a8c50..aeab0ed 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -157,12 +157,12 @@ int main(int argc, char ** argv) { case 'i': { - strncpy(ipAddress, optarg, 32); + memcpy(ipAddress, optarg, 32); break; } case 'c': { - strncpy(chatLogPath, optarg, PATH_MAX + 1); + memcpy(chatLogPath, optarg, PATH_MAX + 1); chatLog = fopen(chatLogPath, "a+"); if (chatLog == NULL) { @@ -176,7 +176,7 @@ int main(int argc, char ** argv) } case 'g': { - strncpy(gameLogPath, optarg, PATH_MAX + 1); + memcpy(gameLogPath, optarg, PATH_MAX + 1); gameLog = fopen(gameLogPath, "a+"); if (gameLog == NULL) { diff --git a/src/gamelogic.c b/src/gamelogic.c index 112291f..b7d4741 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -112,14 +112,17 @@ int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) strtok(messageToQueue->content->messageContent, " "); // Copy the command and arguments to the new commandEvent: - strncpy(newCommand->command, &messageToQueue->content->messageContent[1], 16); - strncpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2], + memcpy(newCommand->command, &messageToQueue->content->messageContent[1], 16); + memcpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2], MAX - (strlen(newCommand->command) + 2)); // Ensure the arguments are safe to parse, without adding newlines: userNameSanatize(newCommand->command, 16); + newCommand->command[15] = '\0'; + userNameSanatize(newCommand->arguments, MAX); - + newCommand->arguments[MAX - 1] = '\0'; + // Lowercase the command for easier comparison: for (char * character = newCommand->command; *character; ++character) { @@ -336,7 +339,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) char requestedPath[32]; if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getAreaFromList(parameters->areaList, 0)) { - strncpy(requestedPath, currentCommand->arguments, 32); + memcpy(requestedPath, currentCommand->arguments, 32); userNameSanatize(requestedPath, 32); requestedPath[31] = '\0'; if(movePlayerToArea(currentCommand->caller, requestedPath) == 0) diff --git a/src/inputoutput.c b/src/inputoutput.c index e6af436..61da91e 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -265,17 +265,17 @@ int dequeueInputMessage(inputMessageQueue * queue) int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer) { // Copy the message into a new input message: - inputMessage * inputMessage = malloc(sizeof(inputMessage)); + inputMessage * input = malloc(sizeof(inputMessage)); // Allocate the internal userMessage to store the message: - inputMessage->content = malloc(sizeof(userMessage)); + input->content = malloc(sizeof(userMessage)); // Copy the userMessage to the internal userMessage: - strncpy(inputMessage->content->senderName, messageToQueue.senderName, 32); - strncpy(inputMessage->content->messageContent, messageToQueue.messageContent, MAX); + strncpy(input->content->senderName, messageToQueue.senderName, 32); + strncpy(input->content->messageContent, messageToQueue.messageContent, MAX); // We have no targets, NULL sends to all players in an area: - inputMessage->sender = sendingPlayer; + input->sender = sendingPlayer; // Wait for the queue to unlock: while (queue->lock); @@ -295,8 +295,8 @@ int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, pla // If the queue is empty, set the first message as both the front and back of the queue: if(queue->front == NULL) { - queue->front = inputMessage; - queue->back = inputMessage; + queue->front = input; + queue->back = input; queue->currentLength++; // Unlock the queue: @@ -306,8 +306,8 @@ int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, pla } else { - queue->back->next = inputMessage; - queue->back = inputMessage; + queue->back->next = input; + queue->back = input; queue->currentLength++; // Unlock the queue: diff --git a/src/playerdata.c b/src/playerdata.c index 50f32e0..8938674 100644 --- a/src/playerdata.c +++ b/src/playerdata.c @@ -58,36 +58,39 @@ int addSkillNode(skillList * skillList, playerSkill * skill) // Remove a skill node from a skill list: int removeSkillNode(skillList * skillList, playerSkill * skill) { + // Check the validity of the pointers: + if(skillList->head == NULL || skill == NULL) + { + return -1; + } + if(skillList->head->skill == skill) { + skillNode * newHead = skillList->head->next; free(skillList->head->skill); - if(skillList->head->next != NULL) - { - skillNode * deletedNode = skillList->head; - skillList->head = skillList->head->next; - free(deletedNode); - return 0; - } - else - { - skillNode * currentNode = skillList->head; - skillNode * previousNode = skillList->head; - while(currentNode->skill != skill) - { - if(currentNode->next == NULL) - { - return -1; - } - previousNode = currentNode; - currentNode = currentNode->next; - } - free(currentNode->skill); - previousNode->next = currentNode->next; - free(currentNode); - return 0; - } + free(skillList->head); + skillList->head = newHead; + return 0; } - return -1; + + else + { + skillNode * currentNode = skillList->head; + skillNode * previousNode = skillList->head; + while(currentNode->skill != skill) + { + if(currentNode->next == NULL) + { + return -1; + } + previousNode = currentNode; + currentNode = currentNode->next; + } + free(currentNode->skill); + previousNode->next = currentNode->next; + free(currentNode); + return 0; + } } // Take a skill and add it to the player's skill list: diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 824de8d..f2d9e1b 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -27,12 +27,14 @@ typedef struct sockaddr sockaddr; void sigintHandler(int signal) { + printf("Caught signal %d.\n", signal); exit(EXIT_SUCCESS); } int main(int argc, char ** argv) { time_t currentTime; + unsigned delay = 4000; int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; @@ -44,6 +46,20 @@ int main(int argc, char ** argv) struct sockaddr_in serverAddress, clientAddress; inputMessageQueue * inputQueue = createInputMessageQueue(); outputMessageQueue * outputQueue = createOutputMessageQueue(); + + // Parse command-line options: + int currentopt = 0; + while ((currentopt = getopt(argc, argv, "d:")) != -1) + { + switch(currentopt) + { + case 'd': + { + delay = atoi(optarg); + break; + } + } + } // Set the handler for SIGINT: signal(2, sigintHandler); @@ -97,8 +113,8 @@ int main(int argc, char ** argv) // -==[ TEST GAME-STATE INITIALIZATION END ]==- // 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.4\n", 5000); + slowPrint(logostring, delay); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.4\n", delay); // Seed random number generator from the current time: srandom((unsigned)time(¤tTime)); @@ -119,7 +135,7 @@ int main(int argc, char ** argv) else { - slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay); } // @@ -139,7 +155,7 @@ int main(int argc, char ** argv) else { - slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", delay); } // Let's start listening: @@ -150,7 +166,7 @@ int main(int argc, char ** argv) } else { - slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", delay); } length = sizeof(clientAddress); @@ -173,7 +189,7 @@ int main(int argc, char ** argv) gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); } - slowPrint("\tTLS Preparation is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tTLS Preparation is:\t\033[32;40mGREEN.\033[0m\n", delay); // Prepare the game logic thread: gameLogicParameters * gameLogicThreadParameters = malloc(sizeof(gameLogicParameters)); @@ -185,8 +201,8 @@ int main(int argc, char ** argv) gameLogicThreadParameters->areaList = areas; pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters); - slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", 5000); - slowPrint("=====\n", 5000); + slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); + slowPrint("=====\n", delay); struct timeval timeout = {0, 500}; while(true) From f3ad758e4f4344b58c2218db0c3eaa46b7e89fd4 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 11 Nov 2022 22:58:05 +0000 Subject: [PATCH 25/53] Preliminary implementation of linked-list.c - Implemented first pass of linked-list.c. TODO: - Polish linked-list.c 'till you can see a reflection in it. - Refactor existing codebase to use linked-list.c.y --- src/linkedlist.c | 467 +++++++++++++++++++++++++++++++++++ src/linkedlist.h | 75 ++++++ src/server/SilverMUDServer.c | 1 + tests/list-test.c | 56 +++-- 4 files changed, 582 insertions(+), 17 deletions(-) create mode 100644 src/linkedlist.c create mode 100644 src/linkedlist.h diff --git a/src/linkedlist.c b/src/linkedlist.c new file mode 100644 index 0000000..7a8ccf8 --- /dev/null +++ b/src/linkedlist.c @@ -0,0 +1,467 @@ +// linkedlist.h: Function definitions for the list type for SilverMUD. +// Barry Kane, 2022. +#include +#include +#include +#include +#include "playerdata.h" +#include "linkedlist.h" + +// Deallocate a given list node, including it's data: +static inline void deallocateListNode(listNode * node, listDataType type) +{ + // Delete the node: + switch(type) + { + case PLAYER: + { + deallocatePlayer(node->data.player); + break; + } + case AREA: + { +// destroyList(node->data.area->paths); + free(node->data.area); + free(node); + break; + } + case PATH: + { + free(node->data.path); + free(node); + break; + } + case SKILL: + { + free(node->data.skill); + free(node); + break; + } + } +} + +// Allocates and instantiates a list of the specified type: +list * createList(listDataType type) +{ + // Allocate and clear the memory for the list: + list * newList = calloc(sizeof(list), 1); + + // Set the appropriate values in the new list: + newList->type = type; + newList->itemCount = 0; + newList->head = NULL; + newList->tail = NULL; + + // Return the new list: + return newList; +} + +// Deallocates a list and all of it's members: +int destroyList(list ** list) +{ + // Check if the list is empty: + if((*list)->itemCount == 0) + { + free(*list); + list = NULL; + return 0; + } + else + { + while((*list)->itemCount > 0) + { + removeFromList((*list), (*list)->type, (*list)->itemCount - 1); + } + free(*list); + *list = NULL; + return 0; + } +} + +// Returns the data at a given index in a list: +listData * getFromList(list * list, size_t listIndex) +{ + // Check that we were given a valid index: + if(listIndex > (list->itemCount - 1)) + { + perror("Invalid index specified.\n"); + return NULL; + } + // Return the head if index is 0: + else if(listIndex == 0) + { + return &list->head->data; + } + // Loop through the entries in the list until we get to the right one: + else + { + listNode * currentNode = list->head; + while(listIndex-- > 0) + { + currentNode = currentNode->next; + } + return ¤tNode->data; + } +} + +// Returns the node at a given index in a list: +listNode * getNodeFromList(list * list, size_t listIndex) +{ + // Check that we were given a valid index: + if(listIndex > (list->itemCount - 1)) + { + perror("Invalid index specified.\n"); + return NULL; + } + // Return the head if index is 0: + else if(listIndex == 0) + { + return list->head; + } + // Loop through the entries in the list until we get to the right one: + else + { + listNode * currentNode = list->head; + while(listIndex-- > 0) + { + currentNode = currentNode->next; + } + return currentNode; + } +} + +// Adds the given data to the end of a list: +listNode * addToList(list * list, void * data, listDataType type) +{ + // Check the type: + if(type != list->type) + { + fprintf(stderr, "Not the correct type for this list.\n"); + return NULL; + } + + // If this is the first item in the list: + if(list->itemCount == 0) + { + // Allocate the new node for the list: + list->head = calloc(1, sizeof(listNode)); + + // Set the appropriate pointers for the list: + list->head->next = NULL; + list->head->previous = NULL; + list->tail = list->head; + + // Add the data to the new node: + switch(type) + { + case PATH: + { + list->head->data.path = (playerPath *)data; + break; + } + case AREA: + { + list->head->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + list->head->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + list->head->data.skill = (playerSkill *)data; + break; + } + } + } + else + { + // Allocate the new node at the end of the list: + list->tail->next = calloc(1, sizeof(listNode)); + + // Add the data to the new node: + switch(type) + { + case PATH: + { + list->tail->next->data.path = (playerPath *)data; + break; + } + case AREA: + { + list->tail->next->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + list->tail->next->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + list->tail->next->data.skill = (playerSkill *)data; + break; + } + } + + // Set the appropriate pointers in the new node: + list->tail->next->previous = list->tail; + + // Set the list's tail to the new tail: + list->tail = list->tail->next; + } + // Increase the count of items in the list: + list->itemCount++; + + // Return the new item in the list: + return list->tail; +} + +// Insert the given data at a given index in the list: +listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex) +{ + // Check that the types are correct: + if(list->type != type) + { + fprintf(stderr, "Types do not match.\n"); + return NULL; + } + + // Handle the special case of adding to the end of the list: + if(listIndex == (list->itemCount - 1)) + { + return addToList(list, data, type); + } + + // Handle the special case of adding to the beginning of the list: + if(listIndex == 0) + { + // Create the new node: + listNode * newNode = calloc(1, sizeof(listNode)); + + // Add the data to the node: + switch(type) + { + case PATH: + { + newNode->data.path = (playerPath *)data; + break; + } + case AREA: + { + newNode->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + newNode->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + newNode->data.skill = (playerSkill *)data; + break; + } + } + + // Place it in the list: + newNode->next = list->head; + newNode->previous = NULL; + list->head->previous = newNode; + list->head = newNode; + list->itemCount++; + + // Return the node: + return newNode; + } + + // Check that the index is valid: + if(listIndex > (list->itemCount - 1)) + { + fprintf(stderr, "Index is invalid for the list.\n"); + return NULL; + } + + // Get the current node at the index: + listNode * currentNode = list->head; + for(size_t index = 0; index < listIndex; index++) + { + currentNode = currentNode->next; + } + + // Get the node before the current node: + listNode * previousNode = currentNode->previous; + + // Create the new node: + previousNode->next = calloc(1, sizeof(listNode)); + currentNode->previous = previousNode->next; + previousNode->next->next = currentNode; + previousNode->next->previous = previousNode; + + // Add the data to the node: + switch(type) + { + case PATH: + { + previousNode->next->data.path = (playerPath *)data; + break; + } + case AREA: + { + previousNode->next->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + previousNode->next->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + previousNode->next->data.skill = (playerSkill *)data; + break; + } + } + list->itemCount++; + return previousNode->next; +} + +// Delete the given data from a list: +bool deleteFromList(list * list, void * data, listDataType type) +{ + size_t index = 0; + if(getIndexFromList(list, data, type, &index) == false) + { + return false; + } + else + { + removeFromList(list, type, index); + return true; + } +} + +// Delete the data from a given point in a list: +int removeFromList(list * list, listDataType type, size_t listIndex) +{ + // Check that we're removing the correct type: + if(list->type != type) + { + return -1; + } + + // Check the list index is valid: + if(listIndex > list->itemCount - 1) + { + return -2; + } + + // The first node in the list: + if(listIndex == 0) + { + // Get the current head and move the list's head on: + listNode * oldHead = list->head; + list->head = list->head->next; + + // If we haven't removed the last item, set the previous pointer + // in the new head to null. + if(list->head != NULL) + { + list->head->previous = NULL; + } + + // Delete the node: + deallocateListNode(oldHead, type); + + // Return the new amount of items in the list: + list->itemCount--; + return list->itemCount; + } + // The last node in the list: + else if(listIndex == (list->itemCount - 1)) + { + // Move the tail up by one: + list->tail = list->tail->previous; + + // Deallocate the former tail: + deallocateListNode(list->tail->next, type); + + // Set the appropriate pointer: + list->tail->next = NULL; + + // Return the new amount of items in the list: + list->itemCount--; + return list->itemCount; + } + // A node in the middle of the list: + else + { + // Get the needed node as a pointer: + listNode * nodeToDelete = getNodeFromList(list, listIndex); + + // Set the appropriate pointers for the surrounding nodes: + nodeToDelete->previous->next = nodeToDelete->next; + nodeToDelete->next->previous = nodeToDelete->previous; + + // Deallocate the node: + deallocateListNode(nodeToDelete, type); + + // Return the new amount of items in the list: + list->itemCount--; + return list->itemCount; + } +} + +// Get the index of a given piece of data in a list: +bool getIndexFromList(list * list, void * data, listDataType type, size_t * index) +{ + // Check the list types are the same: + if(list->type == type) + { + fprintf(stderr, "List types do not match.\n"); + return false; + } + + for(*index = 0; *index < list->itemCount; *index++) + { + switch(type) + { + case AREA: + { + if(getFromList(list, *index)->area == data) + { + return true; + } + break; + } + case PLAYER: + { + if(getFromList(list, *index)->player == data) + { + return true; + } + break; + } + case PATH: + { + if(getFromList(list, *index)->path == data) + { + return true; + } + break; + } + case SKILL: + { + if(getFromList(list, *index)->skill == data) + { + return true; + } + break; + } + } + } + return false; +} + diff --git a/src/linkedlist.h b/src/linkedlist.h new file mode 100644 index 0000000..ce75592 --- /dev/null +++ b/src/linkedlist.h @@ -0,0 +1,75 @@ +// linkedlist.h: Defines the linked list datatype for SilverMUD. +// Barry Kane, 2022. +#ifndef LINKEDLIST_H +#define LINKEDLIST_H +#include "playerdata.h" +#include "areadata.h" + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +typedef enum listDataType +{ + PATH, + AREA, + PLAYER, + SKILL +} listDataType; + +typedef union listData +{ + playerPath * path; + playerArea * area; + playerInfo * player; + playerSkill * skill; +} listData; + +typedef struct listNode listNode; +typedef struct listNode +{ + listData data; + listNode * next; + listNode * previous; +} listNode; + +typedef struct list +{ + listDataType type; + size_t itemCount; + listNode * head; + listNode * tail; +} list; + +// ================== +// -=[ Functions ]=-: +// ================== + +// Allocates and instantiates a list of the specified type: +list * createList(listDataType type); + +// Deallocates a list and all of it's members: +int destroyList(list ** list); + +// Returns the data at a given index in a list: +listData * getFromList(list * list, size_t listIndex); + +// Returns the node at a given index in a list: +listNode * getNodeFromList(list * list, size_t listIndex); + +// Adds the given data to the end of a list: +listNode * addToList(list * list, void * data, listDataType type); + +// Insert the given data at a given index in the list: +listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex); + +// Delete the given data from a list: +bool deleteFromList(list * list, void * data, listDataType type); + +// Delete the data from a given point in a list: +int removeFromList(list * list, listDataType type, size_t listIndex); + +// Get the index of a given piece of data in a list: +bool getIndexFromList(list * list, void * data, listDataType type, size_t * index); + +#endif diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index f2d9e1b..6c4dd69 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -21,6 +21,7 @@ #include "../gamelogic.h" #include "../constants.h" #include "../playerdata.h" +#include "../linkedlist.h" #include "../texteffects.h" #include "../inputoutput.h" diff --git a/tests/list-test.c b/tests/list-test.c index 9fbeb6a..94f3db1 100644 --- a/tests/list-test.c +++ b/tests/list-test.c @@ -1,24 +1,46 @@ -#include "../src/lists.h" -#include "../src/playerdata.h" +#include "../src/linkedlist.h" #include +static inline void printAreaList(list * areaList) +{ + listData * currentData; + for(int index = 0; index < areaList->itemCount; index++) + { + currentData = getFromList(areaList, index); + printf("%d\t| %s - %s\n", index, currentData->area->areaName, currentData->area->areaDescription); + } +} + void main() { - areaNode * areaList = createAreaList(createArea("Test Area A", "This is Test Area A")); - areaNode * counter = areaList; - addAreaNodeToList(areaList, createArea("Test Area B", "This is Test Area B")); - addAreaNodeToList(areaList, createArea("Test Area C", "This is Test Area C")); - for(int index = 0; index <= 2; index++) + list * areaList = createList(AREA); + char areaName[256]; + char areaDescription[256]; + + printf("\n--==[ Generating a list of ten items. ]==--\n\n"); + for(int count = 1; count <= 10; count++) { - printf("%s\n", counter->data->areaName); - counter = counter->next; - } - deleteAreaNodeFromList(areaList, getAreaFromList(areaList, 1)); - addAreaNodeToList(areaList, createArea("Test Area D", "This is Test Area D")); - counter = areaList; - for(int index = 0; index <= 2; index++) - { - printf("%s\n", counter->data->areaName); - counter = counter->next; + sprintf(areaName, "Area %d", count); + sprintf(areaDescription, "This is Area %d.", count); + + addToList(areaList, createArea(areaName, areaDescription) , AREA); } + printAreaList(areaList); + + printf("\n--==[ Inserting items into specific indexes. ]==--\n\n"); + insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 0); + insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 6); + insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 11); + printAreaList(areaList); + + printf("\n--==[ Removing certain areas from the list. ]==--\n\n"); + removeFromList(areaList, AREA, 12); + removeFromList(areaList, AREA, 6); + removeFromList(areaList, AREA, 0); + + printAreaList(areaList); + + destroyList(&areaList); + printf(""); } + From 582a0d02aecc22d2b7274bb00e1a68176fbcdf00 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 13 Nov 2022 00:23:42 +0000 Subject: [PATCH 26/53] Refactored paths to use linked lists. - Removed the code for the now-obsolete-before-actually-being-used pathLists. - playerAreas now contain a list called pathList. - Refactored createArea, createPath, and the /move and /look commands. - Added typedefs to prevent the compiler being unable to link. --- src/areadata.c | 120 +++++++---------------------------------------- src/areadata.h | 5 +- src/gamelogic.c | 52 +++++++++++--------- src/linkedlist.c | 6 +-- src/linkedlist.h | 8 +++- src/playerdata.h | 7 ++- 6 files changed, 68 insertions(+), 130 deletions(-) diff --git a/src/areadata.c b/src/areadata.c index 060781d..23724d9 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -3,6 +3,7 @@ #include #include "areadata.h" #include "playerdata.h" +#include "linkedlist.h" // ==================== // -=[ Area/Paths: ]=-: @@ -22,12 +23,9 @@ playerArea * createArea(char * nameString, char * descriptionString) createdArea->areaName[31] = '\0'; createdArea->areaDescription[MAX - 36] = '\0'; - // Ensure that all the paths are set to NULL: - for(int index = 0; index < 16; index++) - { - createdArea->areaExits[index] = NULL; - } - + // Create a list for the paths in the area: + createdArea->pathList = createList(PATH); + // Return the pointer: return createdArea; } @@ -35,39 +33,24 @@ playerArea * createArea(char * nameString, char * descriptionString) // Create a path between two areas given two areas and two strings: int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription) { - int fromAreaSlot, toAreaSlot; - for(fromAreaSlot = 0; fromAreaSlot < 16; fromAreaSlot++) - { - if(fromArea->areaExits[fromAreaSlot] == NULL) - { - break; - } - if((fromArea->areaExits[fromAreaSlot] != NULL) && (fromAreaSlot == 15)) - { - return 1; - } - } - for(toAreaSlot = 0; toAreaSlot < 32; toAreaSlot++) - { - if(toArea->areaExits[toAreaSlot] == 0) - { - break; - } - if((toArea->areaExits[toAreaSlot] != 0) && (toAreaSlot == 31)) - { - return 2; - } - } + // Allocate the new paths: playerPath * fromPath = malloc(sizeof(playerPath)); playerPath * toPath = malloc(sizeof(playerPath)); - fromArea->areaExits[fromAreaSlot] = fromPath; - toArea->areaExits[toAreaSlot] = toPath; + + // Setup the from path: strncpy(fromPath->pathName, fromDescription, 32 - 1); - fromPath->pathName[31] = '\0'; + fromPath->pathName[31] = '\0'; + fromPath->areaToJoin = toArea; + + // Setup the to path: strncpy(toPath->pathName, toDescription, 32 - 1); - toPath->pathName[31] = '\0'; - fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea; - toArea->areaExits[toAreaSlot]->areaToJoin = fromArea; + toPath->pathName[31] = '\0'; + toPath->areaToJoin = fromArea; + + // Add to the lists: + addToList(fromArea->pathList, fromPath, PATH); + addToList(toArea->pathList, toPath, PATH); + return 0; } @@ -85,16 +68,6 @@ areaNode * createAreaList(playerArea * initialArea) return newAreaList; } -// Create and initialize an pathList: -pathNode * createPathList(playerPath * initialPath) -{ - pathNode * newPathList = malloc(sizeof(pathNode)); - newPathList->data = initialPath; - newPathList->next = NULL; - newPathList->prev = NULL; - return newPathList; -} - // Adds an areaNode to the end of a list, returning it's position: int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) { @@ -134,45 +107,6 @@ int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) return 0; } -// Adds an pathNode to the end of a list, returning it's position: -int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) -{ - pathNode * current; - int index = 0; - current = toList; - while(current->next != NULL) - { - current = current->next; - index++; - } - current->next = malloc(sizeof(pathNode)); - current->next->prev = current; - current->next->data = pathToAdd; - current->next->next = NULL; - return index; -} - -// Removes an pathNode from the list, returning 0 on success and -1 on failure: -int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) -{ - pathNode * current = fromList; - while(current->data != pathToDelete || current->next != NULL) - { - current = current->next; - } - if(current->next == NULL && current->data != pathToDelete) - { - return -1; - } - current->prev->next = current->next; - if(current->next != NULL) - { - current->next->prev = current->prev; - } - free(current); - return 0; -} - // Return the areaNode at the given index from the list: areaNode * getAreaNode(areaNode * fromList, int listIndex) { @@ -191,24 +125,6 @@ areaNode * getAreaNode(areaNode * fromList, int listIndex) return current; } -// Return the pathNode at the given index from the list: -pathNode * getPathNode(pathNode * fromList, int listIndex) -{ - pathNode * current = fromList; - for(int index = 0; index < listIndex; index++) - { - if(current->next != NULL) - { - current = current->next; - } - else - { - return NULL; - } - } - return current; -} - // Return the playerArea of the areaNode at the given index from the list: playerArea * getAreaFromList(areaNode * fromList, int listIndex) { diff --git a/src/areadata.h b/src/areadata.h index 53da6e6..248aa5e 100644 --- a/src/areadata.h +++ b/src/areadata.h @@ -3,6 +3,8 @@ #ifndef AREADATA_H #define AREADATA_H #include "constants.h" +#include "linkedlist.h" + // ==================== // -=[ Area/Paths: ]=-: // ==================== @@ -18,9 +20,10 @@ struct playerPath struct playerArea { + list * pathList; char areaName[32]; char areaDescription[MAX - 35]; - playerPath * areaExits[16]; +// playerPath * areaExits[16]; }; // Create an area given a name and description: diff --git a/src/gamelogic.c b/src/gamelogic.c index b7d4741..20d8f14 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -8,6 +8,7 @@ #include "constants.h" #include "gamelogic.h" #include "playerdata.h" +#include "linkedlist.h" #include "inputoutput.h" // ======================= @@ -364,16 +365,25 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35); queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); bzero(lookMessage, sizeof(userMessage)); - if(currentCommand->caller->currentArea->areaExits[0] != NULL) + + // Loop through the paths and send the appropriate amount of messages: + int charCount = 13; + strncat(lookMessage->messageContent, "You can go:", 13); + + if(currentCommand->caller->currentArea->pathList->itemCount > 0) { - strncat(lookMessage->messageContent, "You can go:", 13); - for(int index = 0; index < 16; index++) + for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++) { - if(currentCommand->caller->currentArea->areaExits[index] != NULL) + if((charCount + 64) >= MAX) { - snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentCommand->caller->currentArea->areaExits[index]->pathName); - strncat(lookMessage->messageContent, formattedString, 64); + queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + bzero(lookMessage, sizeof(userMessage)); + charCount = 0; } + snprintf(formattedString, 64, "\n\t%ld. %s", index + 1, + getFromList(currentCommand->caller->currentArea->pathList, index)->path->pathName); + strncat(lookMessage->messageContent, formattedString, 64); + charCount += 64; } queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); } @@ -469,7 +479,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(statMessage->messageContent, formattedString, 120); if((charCount + 43) >= MAX) { - strncat(statMessage->messageContent, "\n", 2); +// strncat(statMessage->messageContent, "\n", 2); queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); bzero(statMessage, sizeof(userMessage)); charCount = 0; @@ -800,13 +810,13 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski int movePlayerToArea(playerInfo * player, char * requestedPath) { // Check if a number was given first: - int selected = atoi(requestedPath); - if(selected != 0) + size_t selected = atoi(requestedPath); + if(selected != 0 && !(selected > player->currentArea->pathList->itemCount)) { - if(player->currentArea->areaExits[selected - 1] != NULL && - player->currentArea->areaExits[selected - 1]->areaToJoin != NULL) + if(getFromList(player->currentArea->pathList, selected - 1)->path != NULL && + getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL) { - player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin; + player->currentArea = getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin; return 0; } else @@ -816,17 +826,15 @@ int movePlayerToArea(playerInfo * player, char * requestedPath) } // Otherwise search for the description: - for (int index = 0; index < 16; index++) + for (size_t index = 0; index < player->currentArea->pathList->itemCount; index++) { - if(player->currentArea->areaExits[index] != NULL) + if(strncmp(getFromList(player->currentArea->pathList, index)->path->pathName, + requestedPath, 32) == 0) { - if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) - { - printf("%s: %s\n", player->playerName, player->currentArea->areaExits[index]->pathName); - player->currentArea = player->currentArea->areaExits[index]->areaToJoin; - return 0; - } - } - } + printf("%s: %s\n", player->playerName, getFromList(player->currentArea->pathList, index)->path->pathName); + player->currentArea = getFromList(player->currentArea->pathList, index)->path->areaToJoin; + return 0; + } + } return 1; } diff --git a/src/linkedlist.c b/src/linkedlist.c index 7a8ccf8..bbe17ef 100644 --- a/src/linkedlist.c +++ b/src/linkedlist.c @@ -90,7 +90,7 @@ listData * getFromList(list * list, size_t listIndex) // Return the head if index is 0: else if(listIndex == 0) { - return &list->head->data; + return &(list->head->data); } // Loop through the entries in the list until we get to the right one: else @@ -100,7 +100,7 @@ listData * getFromList(list * list, size_t listIndex) { currentNode = currentNode->next; } - return ¤tNode->data; + return &(currentNode->data); } } @@ -424,7 +424,7 @@ bool getIndexFromList(list * list, void * data, listDataType type, size_t * inde return false; } - for(*index = 0; *index < list->itemCount; *index++) + for(*index = 0; *index < list->itemCount; *index += 1) { switch(type) { diff --git a/src/linkedlist.h b/src/linkedlist.h index ce75592..2f15653 100644 --- a/src/linkedlist.h +++ b/src/linkedlist.h @@ -2,8 +2,14 @@ // Barry Kane, 2022. #ifndef LINKEDLIST_H #define LINKEDLIST_H -#include "playerdata.h" #include "areadata.h" +#include "playerdata.h" + +// Let the compiler know there will be structs defined elsewhere: +typedef struct playerPath playerPath; +typedef struct playerArea playerArea; +typedef struct playerInfo playerInfo; +typedef struct playerSkill playerSkill; // ======================== // -=[ Data Structures ]=-: diff --git a/src/playerdata.h b/src/playerdata.h index 01564b2..ba9c6b1 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -6,6 +6,11 @@ #include #include "areadata.h" #include "constants.h" +#include "linkedlist.h" + +// Let the compiler know there will be structs defined elsewhere: +typedef struct playerPath playerPath; +typedef struct playerArea playerArea; typedef struct statBlock { @@ -49,7 +54,7 @@ typedef struct skillList skillNode * head; int skillCount; } skillList; -\ + typedef struct playerInfo { char playerName[32]; From d843f0b170e5c58f5ed7ce0c762d7887449c566f Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 13 Nov 2022 18:21:06 +0000 Subject: [PATCH 27/53] Added one-way path function. - Added a function to create one way paths. - Changed the test areas to a new setting and to include one-way paths. --- src/areadata.c | 17 +++++++++++++++++ src/areadata.h | 3 +++ src/server/SilverMUDServer.c | 35 +++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/areadata.c b/src/areadata.c index 23724d9..45c0caa 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -54,6 +54,23 @@ int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescriptio return 0; } +// Create a one-way path between two areas given two areas and a string: +int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description) +{ + // Allocate the new paths: + playerPath * path = calloc(1, sizeof(playerPath)); + + // Setup the path: + strncpy(path->pathName, description, 32 - 1); + path->pathName[31] = '\0'; + path->areaToJoin = toArea; + + // Add to the list: + addToList(fromArea->pathList, path, PATH); + + return 0; +} + // ========================= // -=[ Area/Path Lists: ]=-: // ========================= diff --git a/src/areadata.h b/src/areadata.h index 248aa5e..6edde90 100644 --- a/src/areadata.h +++ b/src/areadata.h @@ -32,6 +32,9 @@ playerArea * createArea(char * nameString, char * descriptionString); // Create a path between two areas given two areas and two strings: int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); +// Create a one-way path between two areas given two areas and a string: +int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description); + // ========================= // -=[ Area/Path Lists: ]=-: // ========================= diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 6c4dd69..b7a4161 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -68,23 +68,26 @@ int main(int argc, char ** argv) // -==[ TEST GAME-STATE INITIALIZATION ]==- // Initialize test areas: areaNode * areas = createAreaList(createArea("Login Area", "Please login with the /join command.")); - addAreaNodeToList(areas, createArea("Temple Entrance", - "You are standing outside a large, elaborate temple, of white marble and delicate construction. " - "Etched onto the left pillar next to the large opening is the same symbol, over and over again, a gentle curve with it's ends pointing to the right. " - "A similar symbol is on the right pillar, but it's ends are pointing to the left. ")); - - addAreaNodeToList(areas, createArea("The Hall of Documentation", - "Just past the threshold of the entrance lies a large hall, with bookshelves lining the walls, ceiling to floor. " - "The shelves are filled to the brim with finely-bound books, each with titles in silver lettering on the spine. " - "There are countless books, but you notice a large lectern in the center of the room, and a path leading upwards at the back. ")); - - addAreaNodeToList(areas, createArea("Monument to GNU", - "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.")); + addAreaNodeToList(areas, createArea("Octal One - Docking Bay Alpha", + "You are standing in the main docking bay of the largest station in the Octal System. " + "The sheer size of the bay is awe-inpiring. The number of ships is endless. " + "The bay is curved along with the body of the station. A catwalk runs along the back wall of the bay. " + "Two large arches lie at each end, leading to the other bays, and in the center, a set of doors leading to the interior of the station.")); + + addAreaNodeToList(areas, createArea("Octal One - Station Access Control", + "You enter into the hallway leading to the main interior of the station." + "The attendant informs you that due to a computer error, exits cannot be proccessed at the moment, so you will be unable to leave, until it is resolved. " + "He apologizes profusely for the inconvenience, and clears you for entry if you wish to continue.")); + + addAreaNodeToList(areas, createArea("Octal One - Floor Zero", + "You've never quite seen so many people in one place. A large ring of shopfronts surrounds an area filled with benches and tables. " + "There's so many buisnesses in sight that you feel you could find everything you need, and this is only one of 25 main floors, " + "not to mention the 6 outer pylons which surround the main hull of the station. Staircases lead to an upper platform allowing access to the pylons. ")); + // Initialize test paths: - createPath(getAreaFromList(areas, 1), getAreaFromList(areas, 2), "Go inside the temple.", "Leave the temple."); - createPath(getAreaFromList(areas, 3), getAreaFromList(areas, 2), "Back to the Hall of Documentation.", "Path to Enlightenment."); + createPath(getAreaFromList(areas, 1), getAreaFromList(areas, 2), "Enter the station interior.", "Return to Docking Bay Alpha."); + createOneWayPath(getAreaFromList(areas, 2), getAreaFromList(areas, 3), "Continue to station interior. "); + skillList * globalSkillList = malloc(sizeof(skillList)); globalSkillList->head = NULL; From 6b3d9febf64b4083ec6b3a464aac7d4dcd3df5ea Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 13 Nov 2022 18:26:36 +0000 Subject: [PATCH 28/53] Linked lists now destroy the pathList of an area. - Modified destroyList to destroy pathLists in areas. --- src/linkedlist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linkedlist.c b/src/linkedlist.c index bbe17ef..a11f9b3 100644 --- a/src/linkedlist.c +++ b/src/linkedlist.c @@ -20,7 +20,7 @@ static inline void deallocateListNode(listNode * node, listDataType type) } case AREA: { -// destroyList(node->data.area->paths); + destroyList(&(node->data.area->pathList)); free(node->data.area); free(node); break; From 51f1a953e71be24c827a8aa2b120fb277eb3f3d1 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 18 Nov 2022 14:44:25 +0000 Subject: [PATCH 29/53] Refactored areas to use linked-lists. - Refactored the server to rely on the linked-list version of area lists. - Removed all old code pertaining to Area/Path lists. - Removed a no-longer useful test for corestat-from string performance.y --- src/areadata.c | 78 ------------------------------------ src/areadata.h | 48 ---------------------- src/client/SilverMUDClient.c | 10 ++--- src/gamelogic.c | 10 ++--- src/gamelogic.h | 2 +- src/server/SilverMUDServer.c | 42 ++++++++++--------- tests/corestat-from-string.c | 11 ----- 7 files changed, 35 insertions(+), 166 deletions(-) delete mode 100644 tests/corestat-from-string.c diff --git a/src/areadata.c b/src/areadata.c index 45c0caa..50a61b9 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -70,81 +70,3 @@ int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * descript return 0; } - -// ========================= -// -=[ Area/Path Lists: ]=-: -// ========================= - -// Create and initialize an areaList: -areaNode * createAreaList(playerArea * initialArea) -{ - areaNode * newAreaList = malloc(sizeof(areaNode)); - newAreaList->data = initialArea; - newAreaList->next = NULL; - newAreaList->prev = NULL; - return newAreaList; -} - -// Adds an areaNode to the end of a list, returning it's position: -int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) -{ - areaNode * current; - int index = 0; - current = toList; - while(current->next != NULL) - { - current = current->next; - index++; - } - current->next = malloc(sizeof(areaNode)); - current->next->prev = current; - current->next->data = areaToAdd; - current->next->next = NULL; - return 0; -} - -// Removes an areaNode from the list, returning 0 on success and -1 on failure: -int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) -{ - areaNode * current = fromList; - while(current->data != areaToDelete && current->next != NULL) - { - current = current->next; - } - if(current->next == NULL && current->data != areaToDelete) - { - return -1; - } - current->prev->next = current->next; - if(current->next != NULL) - { - current->next->prev = current->prev; - } - free(current); - return 0; -} - -// Return the areaNode at the given index from the list: -areaNode * getAreaNode(areaNode * fromList, int listIndex) -{ - areaNode * current = fromList; - for(int index = 0; index < listIndex; index++) - { - if(current->next != NULL) - { - current = current->next; - } - else - { - return NULL; - } - } - return current; -} - -// Return the playerArea of the areaNode at the given index from the list: -playerArea * getAreaFromList(areaNode * fromList, int listIndex) -{ - areaNode * current = getAreaNode(fromList, listIndex); - return current->data; -} diff --git a/src/areadata.h b/src/areadata.h index 6edde90..e983019 100644 --- a/src/areadata.h +++ b/src/areadata.h @@ -35,54 +35,6 @@ int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescriptio // Create a one-way path between two areas given two areas and a string: int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description); -// ========================= -// -=[ Area/Path Lists: ]=-: -// ========================= - -typedef struct areaNode areaNode; -typedef struct pathNode pathNode; - -struct pathNode -{ - playerPath * data; - pathNode * next; - pathNode * prev; -}; - -struct areaNode -{ - playerArea * data; - areaNode * next; - areaNode * prev; -}; - -// Create and initialize an areaList: -areaNode * createAreaList(playerArea * initialArea); - -// Create and initialize an pathList: -pathNode * createPathList(playerPath * initialPath); - -// Adds an areaNode to the end of a list, returning it's position: -int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd); - -// Removes an areaNode from the list, returning 0 on success and -1 on failure: -int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete); - -// Adds an pathNode to the end of a list, returning it's position: -int addPathNodeToList(pathNode * toList, playerPath * pathToAdd); - -// Removes an pathNode from the list, returning 0 on success and -1 on failure: -int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete); - -// Return the areaNode at the given index from the list: -areaNode * getAreaNode(areaNode * fromList, int listIndex); - -// Return the pathNode at the given index from the list: -pathNode * getPathNode(pathNode * fromList, int listIndex); - -// Return the playerArea of the areaNode at the given index from the list: -playerArea * getAreaFromList(areaNode * fromList, int listIndex); - // TO BE IMPLEMENTED: /* int saveAreaList(areaNode * listToSave); */ diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index aeab0ed..9a26cbd 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -125,7 +125,7 @@ void * messageReceiver(void * parameters) serverMessage = false; } slowPrintNcurses(receiveBuffer.senderName, 4000, threadParameters->window, true); - slowPrintNcurses(": ", 4000, threadParameters->window, true); + slowPrintNcurses(": ", 4000, threadParameters->window, true); slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); } } @@ -156,7 +156,7 @@ int main(int argc, char ** argv) switch (currentopt) { case 'i': - { + { memcpy(ipAddress, optarg, 32); break; } @@ -192,7 +192,7 @@ int main(int argc, char ** argv) { port = atoi(optarg); break; - } + } case 'd': { characterDelay = atoi(optarg); @@ -225,7 +225,7 @@ int main(int argc, char ** argv) serverAddress.sin_port = htons(port); // Connect the server and client sockets, Kronk: - if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) + if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) { slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay); exit(0); @@ -256,7 +256,7 @@ int main(int argc, char ** argv) // Use the default for the GnuTLS handshake timeout: gnutls_handshake_set_timeout(tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - // Repeatedly attempt to handshake unless we encounter a fatal error: + // Repeatedly attempt to handshake unless we encounter a fatal error: int returnValue = -1; do { diff --git a/src/gamelogic.c b/src/gamelogic.c index 20d8f14..73be73b 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -40,8 +40,8 @@ void * gameLogicLoop(void * parameters) { queueMessagedCommand(commandQueue, currentInput); } - else if(currentInput->sender->currentArea == getAreaFromList(threadParameters->areaList, 0)) - { + else if(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area) + { currentInput = NULL; threadParameters->inputQueue->lock = false; dequeueInputMessage(threadParameters->inputQueue); @@ -338,7 +338,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) if(strncmp(currentCommand->command, "move", 4) == 0) { char requestedPath[32]; - if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getAreaFromList(parameters->areaList, 0)) + if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area) { memcpy(requestedPath, currentCommand->arguments, 32); userNameSanatize(requestedPath, 32); @@ -393,7 +393,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) // TODO: Implement login/character creation. Will be a while: if(strncmp(currentCommand->command, "join", 4) == 0) { - if(currentCommand->caller->currentArea == getAreaFromList(parameters->areaList, 0)) + if(currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area) { bool validName = true; for(int index = 0; index < *parameters->playerCount; index++) @@ -410,7 +410,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) if(validName) { strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16); - currentCommand->caller->currentArea = getAreaFromList(parameters->areaList, 1); + currentCommand->caller->currentArea = getFromList(parameters->areaList, 1)->area; // Call the look command after joining. It's fine to unlock, because the loop won't // continue until the command is queued: queue->lock = false; diff --git a/src/gamelogic.h b/src/gamelogic.h index 7660063..26a910d 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -16,7 +16,7 @@ typedef struct gameLogicParameters { int * playerCount; - areaNode * areaList; + list * areaList; playerInfo * connectedPlayers; inputMessageQueue * inputQueue; outputMessageQueue * outputQueue; diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index b7a4161..01fd0d6 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -67,26 +67,32 @@ int main(int argc, char ** argv) // -==[ TEST GAME-STATE INITIALIZATION ]==- // Initialize test areas: - areaNode * areas = createAreaList(createArea("Login Area", "Please login with the /join command.")); - addAreaNodeToList(areas, createArea("Octal One - Docking Bay Alpha", - "You are standing in the main docking bay of the largest station in the Octal System. " - "The sheer size of the bay is awe-inpiring. The number of ships is endless. " - "The bay is curved along with the body of the station. A catwalk runs along the back wall of the bay. " - "Two large arches lie at each end, leading to the other bays, and in the center, a set of doors leading to the interior of the station.")); + list * areas = createList(AREA); + addToList(areas, createArea("Login Area", "Please login with the /join command."), AREA); - addAreaNodeToList(areas, createArea("Octal One - Station Access Control", - "You enter into the hallway leading to the main interior of the station." - "The attendant informs you that due to a computer error, exits cannot be proccessed at the moment, so you will be unable to leave, until it is resolved. " - "He apologizes profusely for the inconvenience, and clears you for entry if you wish to continue.")); + // Create the areas: + addToList(areas, createArea("Octal One - Docking Bay Alpha", + "You are standing in the main docking bay of the largest station in the Octal System. " + "The sheer size of the bay is awe-inpiring. The number of ships is endless. " + "The bay is curved along with the body of the station. A catwalk runs along the back wall of the bay. " + "Two large arches lie at each end, leading to the other bays, and in the center, a set of doors leading to the interior of the station."), AREA); + + addToList(areas, createArea("Octal One - Station Access Control", + "You enter into the hallway leading to the main interior of the station." + "The attendant informs you that due to a computer error, exits cannot be proccessed at the moment," + " so you will be unable to leave, until it is resolved. " + "He apologizes profusely for the inconvenience, and clears you for entry if you wish to continue."), AREA); - addAreaNodeToList(areas, createArea("Octal One - Floor Zero", - "You've never quite seen so many people in one place. A large ring of shopfronts surrounds an area filled with benches and tables. " - "There's so many buisnesses in sight that you feel you could find everything you need, and this is only one of 25 main floors, " - "not to mention the 6 outer pylons which surround the main hull of the station. Staircases lead to an upper platform allowing access to the pylons. ")); + addToList(areas, createArea("Octal One - Floor Zero", + "You've never quite seen so many people in one place. A large ring of shopfronts surrounds an area filled with benches and tables. " + "There's so many buisnesses in sight that you feel you could find everything you need, and this is only one of 25 main floors, " + "not to mention the 6 outer pylons which surround the main hull of the station. Staircases lead to an upper platform allowing access to the pylons. "), AREA); // Initialize test paths: - createPath(getAreaFromList(areas, 1), getAreaFromList(areas, 2), "Enter the station interior.", "Return to Docking Bay Alpha."); - createOneWayPath(getAreaFromList(areas, 2), getAreaFromList(areas, 3), "Continue to station interior. "); + createPath(getFromList(areas, 1)->area, getFromList(areas, 2)->area, + "Enter the station interior.", "Return to Docking Bay Alpha."); + createOneWayPath(getFromList(areas, 2)->area, getFromList(areas, 3)->area, + "Continue to station interior. "); skillList * globalSkillList = malloc(sizeof(skillList)); globalSkillList->head = NULL; @@ -106,7 +112,7 @@ int main(int argc, char ** argv) // OH NO IT'S NOT MEMORY SAFE BETTER REWRITE IT IN RUST // But wait, we know the string won't be too big, so it's fine. strcpy(connectedPlayers[index].playerName, testString); - connectedPlayers[index].currentArea = getAreaFromList(areas, 0); + connectedPlayers[index].currentArea = getFromList(areas, 0)->area; connectedPlayers[index].stats = calloc(1, sizeof(statBlock)); connectedPlayers[index].stats->specPoints = 30; connectedPlayers[index].stats->skillPoints = 30; @@ -300,7 +306,7 @@ int main(int argc, char ** argv) // Clear out the old player state so that a new one may join: sprintf(testString, "UNNAMED %d", index); strcpy(connectedPlayers[index].playerName, testString); - connectedPlayers[index].currentArea = getAreaFromList(areas, 0); + connectedPlayers[index].currentArea = getFromList(areas, 0)->area; // Prepare a fresh SSL session for the next new player: gnutls_init(&tlssessions[index], GNUTLS_SERVER); diff --git a/tests/corestat-from-string.c b/tests/corestat-from-string.c deleted file mode 100644 index ab70d82..0000000 --- a/tests/corestat-from-string.c +++ /dev/null @@ -1,11 +0,0 @@ -// Hopefully optimized corestat to string: -#include -#include -#include -#include -#include "../src/playerdata.h" - -void main(int argc, char ** argv) -{ - getCoreStatFromString(argv[1], strlen(argv[1])); -} From 4cc0d3a0f61e66f38872ffbebd7ec8f853819909 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Tue, 29 Nov 2022 21:04:36 +0000 Subject: [PATCH 30/53] Completed the conversion to the new linked-list type. - Moved all code relating to skills to use the new linked-lists. - Removed all old code relating to other lists. - Improved linked lists to get nodes more efficiently in the second half of the list. --- src/gamelogic.c | 59 +++++++-------- src/gamelogic.h | 4 +- src/linkedlist.c | 20 ++++- src/playerdata.c | 140 +++++++++-------------------------- src/playerdata.h | 30 ++------ src/server/SilverMUDServer.c | 6 +- 6 files changed, 86 insertions(+), 173 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index 73be73b..58b2d82 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -468,13 +468,15 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) bzero(statMessage->messageContent, sizeof(char) * MAX); if(currentCommand->caller->skills->head != NULL) { - skillNode * currentSkill = currentCommand->caller->skills->head; + size_t skillIndex = 0; int charCount = 0; bool addNewline = false; - while(currentSkill != NULL) + playerSkill * skill; + while(skillIndex < currentCommand->caller->skills->itemCount) { - snprintf(formattedString, 120, "| %2d | %31s ", - currentSkill->skill->skillPoints, currentSkill->skill->skillName); + skill = getFromList(currentCommand->caller->skills, skillIndex)->skill; + skillIndex++; + snprintf(formattedString, 120, "| %2d | %31s ", skill->skillPoints, skill->skillName); charCount += 43; strncat(statMessage->messageContent, formattedString, 120); if((charCount + 43) >= MAX) @@ -494,9 +496,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) else { addNewline = true; - } - currentSkill = currentSkill->next; - + } } queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); } @@ -617,14 +617,16 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) } if(strncmp(currentCommand->command, "listskills", 10) == 0) { - skillNode * currentSkill = parameters->globalSkillList->head; userMessage * listMessage = calloc(1, sizeof(userMessage)); char * formattedString = calloc(121, sizeof(char)); int charCount = 0; + size_t skillIndex = 0; bool addNewline = false; - while(currentSkill != NULL) + playerSkill * currentSkill; + while(skillIndex < parameters->globalSkillList->itemCount) { - snprintf(formattedString, 120, "| %-31s ", currentSkill->skill->skillName); + currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill; + snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName); charCount += 43; strncat(listMessage->messageContent, formattedString, 120); if((charCount + 46) >= MAX) @@ -644,7 +646,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { addNewline = true; } - currentSkill = currentSkill->next; + skillIndex++; } queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); free(listMessage); @@ -727,7 +729,7 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } // Run a skill check: -outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList) +outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList) { // Calculate the chance: if(chance > 100 || chance < 0) @@ -738,46 +740,39 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski // Check if the player has the given skill: bool playerHasSkill = false; - skillNode * currentPlayerNode = player->skills->head; - while(currentPlayerNode != NULL) + size_t playerIndex = 0; + while(playerIndex < player->skills->itemCount) { - if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0) + if(strncmp(skillName, getFromList(player->skills, playerIndex)->skill->skillName, skillNameLength) != 0) { playerHasSkill = true; break; } - currentPlayerNode = currentPlayerNode->next; + playerIndex++; } // If the player doesn't have the skill, check if it's in the game and is trained: bool trainedSkill = false; - if(!playerHasSkill) + size_t globalIndex = 0; + while(globalIndex < globalSkillList->itemCount) { - skillNode * currentNode = globalSkillList->head; - while(strncmp(skillName, currentNode->skill->skillName, 32) != 0) + if(strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) != 0) { - if(currentNode->next == NULL) - { - fprintf(stderr, "Skill doesn't exist in skill list.\n"); - return ERROR; - } - currentNode = currentNode->next; - } - if(currentNode->skill->trainedSkill == true) - { - trainedSkill = true; + trainedSkill = getFromList(globalSkillList, globalIndex)->skill->trainedSkill; + break; } + globalIndex++; } - + // Calculate the modifier: int modifier = 0; if(trainedSkill) { modifier = -100; } - else + else if(playerHasSkill) { - modifier = currentPlayerNode->skill->skillPoints * 4; + modifier = getFromList(player->skills, playerIndex)->skill->skillModifier * 4; } // Attempt the check: diff --git a/src/gamelogic.h b/src/gamelogic.h index 26a910d..e916d1b 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -20,7 +20,7 @@ typedef struct gameLogicParameters playerInfo * connectedPlayers; inputMessageQueue * inputQueue; outputMessageQueue * outputQueue; - skillList * globalSkillList; + list * globalSkillList; } gameLogicParameters; // Thread function which runs the main game loop, given the needed parameters: @@ -90,6 +90,6 @@ typedef enum outcome outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); // Run a skill check: -outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList); +outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList); #endif diff --git a/src/linkedlist.c b/src/linkedlist.c index a11f9b3..71154e3 100644 --- a/src/linkedlist.c +++ b/src/linkedlist.c @@ -121,12 +121,24 @@ listNode * getNodeFromList(list * list, size_t listIndex) // Loop through the entries in the list until we get to the right one: else { - listNode * currentNode = list->head; - while(listIndex-- > 0) + if((list->itemCount / 2) < listIndex) { - currentNode = currentNode->next; + listNode * currentNode = list->tail; + while(listIndex-- > 0) + { + currentNode = currentNode->previous; + } + return currentNode; + } + else + { + listNode * currentNode = list->head; + while(listIndex-- > 0) + { + currentNode = currentNode->next; + } + return currentNode; } - return currentNode; } } diff --git a/src/playerdata.c b/src/playerdata.c index 8938674..3add36e 100644 --- a/src/playerdata.c +++ b/src/playerdata.c @@ -9,12 +9,12 @@ #include "playerdata.h" // Create a new skill and add it to the global skill list: -int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) +listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) { if(skillNameLength >= 32) { fprintf(stderr, "Skill name is too long. Please shorten the name and try again.\n"); - return -1; + return NULL; } playerSkill * newSkill = malloc(sizeof(playerSkill)); @@ -26,112 +26,56 @@ int createSkill(skillList * globalSkillList, char * skillName, int skillNameLeng newSkill->trainedSkill = trainedSkill; // Add the skill to a node in the list: - return(addSkillNode(globalSkillList, newSkill)); -} - -// Add a skill node to a skill list: -int addSkillNode(skillList * skillList, playerSkill * skill) -{ - if(skillList->head == NULL) - { - skillList->head = malloc(sizeof(skillNode)); - skillList->head->skill = skill; - skillList->head->next = NULL; - skillList->skillCount = 1; - return 0; - } - else - { - skillNode * currentNode = skillList->head; - while(currentNode->next != NULL) - { - currentNode = currentNode->next; - } - currentNode->next = malloc(sizeof(skillNode)); - currentNode->next->skill = skill; - currentNode->next->next = NULL; - skillList->skillCount++; - return skillList->skillCount; - } -} - -// Remove a skill node from a skill list: -int removeSkillNode(skillList * skillList, playerSkill * skill) -{ - // Check the validity of the pointers: - if(skillList->head == NULL || skill == NULL) - { - return -1; - } - - if(skillList->head->skill == skill) - { - skillNode * newHead = skillList->head->next; - free(skillList->head->skill); - free(skillList->head); - skillList->head = newHead; - return 0; - } - - else - { - skillNode * currentNode = skillList->head; - skillNode * previousNode = skillList->head; - while(currentNode->skill != skill) - { - if(currentNode->next == NULL) - { - return -1; - } - previousNode = currentNode; - currentNode = currentNode->next; - } - free(currentNode->skill); - previousNode->next = currentNode->next; - free(currentNode); - return 0; - } + return(addToList(globalSkillList, newSkill, SKILL)); } // Take a skill and add it to the player's skill list: -int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer) +int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer) { - - skillNode * currentNode = globalSkillList->head; - while(strncmp(skillName, currentNode->skill->skillName, 32) != 0) + // Check if the skill exists in the game: + size_t globalIndex = 0; + bool skillExists = false; + while(globalIndex < globalSkillList->itemCount) { - if(currentNode->next == NULL) + if(strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) == 0) { - fprintf(stderr, "Skill doesn't exist in skill list.\n"); - return -1; + skillExists = true; + break; } - currentNode = currentNode->next; + globalIndex++; } - bool playerHasSkill = false; - skillNode * currentPlayerNode = targetPlayer->skills->head; - while(currentPlayerNode != NULL) + if(!skillExists) { - if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0) + fprintf(stderr, "Skill doesn't exist in skill list.\n"); + return -1; + } + + // Check if the player has the skill: + size_t playerIndex = 0; + bool playerHasSkill = false; + while(playerIndex < targetPlayer->skills->itemCount) + { + if(strncmp(skillName, getFromList(targetPlayer->skills, playerIndex)->skill->skillName, skillNameLength) == 0) { playerHasSkill = true; break; } - currentPlayerNode = currentPlayerNode->next; + playerIndex++; } if(playerHasSkill) { - currentPlayerNode->skill->skillPoints++; + getFromList(targetPlayer->skills, playerIndex)->skill->skillPoints++; } + + // Copy the skill into the player's skill list: else { - addSkillNode(targetPlayer->skills, currentNode->skill); - currentPlayerNode = targetPlayer->skills->head; - while(currentPlayerNode->next != NULL) - { - currentPlayerNode = currentPlayerNode->next; - } - currentPlayerNode->skill->skillPoints = 1; + playerSkill * newSkill = calloc(1, sizeof(playerSkill)); + strncpy(newSkill->skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, 32); + printf("%s ", newSkill->skillName); + newSkill->skillPoints = 1; + addToList(targetPlayer->skills, newSkill, SKILL); } return 0; } @@ -268,25 +212,7 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) int deallocatePlayer(playerInfo * playerToDeallocate) { // Deallocate the skill list: - if(playerToDeallocate->skills->skillCount > 0) - { - // Allocate enough pointers: - skillNode * nodesToDeallocate[playerToDeallocate->skills->skillCount]; - skillNode * currentSkillNode = playerToDeallocate->skills->head; - - // Get a list of all the nodes together: - for(int index = 0; index < playerToDeallocate->skills->skillCount; index++) - { - nodesToDeallocate[index] = currentSkillNode; - currentSkillNode = currentSkillNode->next; - } - - // Deallocate all the nodes: - for(int index = 0; index < playerToDeallocate->skills->skillCount; index++) - { - free(nodesToDeallocate[index]); - } - } + destroyList(&(playerToDeallocate->skills)); // Deallocate the stat block: free(playerToDeallocate->stats); diff --git a/src/playerdata.h b/src/playerdata.h index ba9c6b1..e1d679f 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -11,6 +11,8 @@ // Let the compiler know there will be structs defined elsewhere: typedef struct playerPath playerPath; typedef struct playerArea playerArea; +typedef struct list list; +typedef struct listNode listNode; typedef struct statBlock { @@ -42,25 +44,12 @@ typedef struct playerSkill bool trainedSkill; } playerSkill; -typedef struct skillNode skillNode; -struct skillNode -{ - playerSkill * skill; - skillNode * next; -}; - -typedef struct skillList -{ - skillNode * head; - int skillCount; -} skillList; - typedef struct playerInfo { char playerName[32]; playerArea * currentArea; statBlock * stats; - skillList * skills; + list * skills; } playerInfo; typedef enum coreStat @@ -74,18 +63,11 @@ typedef enum coreStat } coreStat; // Create a new skill and add it to the global skill list: -int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill); - -// Add a skill node to a skill list: -int addSkillNode(skillList * skillList, playerSkill * skill); - -// Remove a skill node from a skill list: -int removeSkillNode(skillList * skillList, playerSkill * skill); -int removeSkillByID(skillList * skillList, playerSkill * skill); +listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill); // Take a skill and add it to the player's skill list: -int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer); -int takeSkillbyID(skillList * globalSkillList, int skillID, playerInfo * targetPlayer); +int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer); +int takeSkillbyID(list * globalSkillList, int skillID, playerInfo * targetPlayer); // Take a string containing a core stat name and return the core stat: coreStat getCoreStatFromString(char * string, int stringLength); diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 01fd0d6..eddec7d 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -94,8 +94,7 @@ int main(int argc, char ** argv) createOneWayPath(getFromList(areas, 2)->area, getFromList(areas, 3)->area, "Continue to station interior. "); - skillList * globalSkillList = malloc(sizeof(skillList)); - globalSkillList->head = NULL; + list * globalSkillList = createList(SKILL); // Create a few basic skills: createSkill(globalSkillList, "Medicine", 8, true); @@ -116,8 +115,7 @@ int main(int argc, char ** argv) connectedPlayers[index].stats = calloc(1, sizeof(statBlock)); connectedPlayers[index].stats->specPoints = 30; connectedPlayers[index].stats->skillPoints = 30; - connectedPlayers[index].skills = calloc(1, sizeof(skillList)); - connectedPlayers[index].skills->head = NULL; + connectedPlayers[index].skills = createList(SKILL); } // -==[ TEST GAME-STATE INITIALIZATION END ]==- From 2ab873b40b9ce6c9bf7edc38609c50f267318181 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 30 Nov 2022 15:31:14 +0000 Subject: [PATCH 31/53] Make the client respect the set character delay. - The client now respects the character delay set on the command line. --- src/client/SilverMUDClient.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index 9a26cbd..be8b3da 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -26,6 +26,7 @@ typedef struct threadparameters FILE * loggingStream; bool loggingFlag; WINDOW * window; + int characterDelay; } threadparameters; // Use sockaddr as a type: @@ -100,17 +101,16 @@ void * messageReceiver(void * parameters) } if(serverMessage == false) { - slowPrintNcurses("\n --====<>====--", 4000, threadParameters->window, true); + slowPrintNcurses("\n --====<>====--", threadParameters->characterDelay, threadParameters->window, true); serverMessage = true; } - slowPrintNcurses("\n", 4000, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); - slowPrintNcurses("\n", 4000, threadParameters->window, true); + slowPrintNcurses("\n", threadParameters->characterDelay, threadParameters->window, true); + slowPrintNcurses(receiveBuffer.messageContent, threadParameters->characterDelay, threadParameters->window, false); + slowPrintNcurses("\n", threadParameters->characterDelay, threadParameters->window, true); } else { - wrapString(receiveBuffer.messageContent, - strlen(receiveBuffer.messageContent) - 1, + wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth - strlen(receiveBuffer.senderName) - 2); if (threadParameters->loggingFlag == true) { @@ -121,12 +121,12 @@ void * messageReceiver(void * parameters) } if(serverMessage == true) { - slowPrintNcurses("\n --====<>====-- \n", 4000, threadParameters->window, true); + slowPrintNcurses("\n --====<>====-- \n", threadParameters->characterDelay, threadParameters->window, true); serverMessage = false; } - slowPrintNcurses(receiveBuffer.senderName, 4000, threadParameters->window, true); - slowPrintNcurses(": ", 4000, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); + slowPrintNcurses(receiveBuffer.senderName, threadParameters->characterDelay, threadParameters->window, true); + slowPrintNcurses(": ", threadParameters->characterDelay, threadParameters->window, true); + slowPrintNcurses(receiveBuffer.messageContent, threadParameters->characterDelay, threadParameters->window, false); } } pthread_exit(NULL); @@ -278,6 +278,7 @@ int main(int argc, char ** argv) logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); logArea->tlsSession = tlsSession; logArea->loggingFlag = chatLogging; + logArea->characterDelay = characterDelay; if (chatLog != NULL) { logArea->loggingStream = chatLog; From 8ae3eaf2b844390512deb9fc46159635562130d1 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 18 Dec 2022 21:07:10 +0000 Subject: [PATCH 32/53] Added queue.c and queue.h. - Added a singular queue type for input/output messages and commands. - Added a test of the queue type in tests. - Made some edits to the Makefile to enable the addition of debug code using the preprocessor. - Minor styling and spelling updates in gamelogic.c/h and text effects .h. - Ready to integrate the new queue type in place of the previous ones. --- Makefile | 2 +- src/gamelogic.c | 2 + src/gamelogic.h | 5 +- src/linkedlist.h | 6 +- src/queue.c | 206 +++++++++++++++++++++++++++++++++++ src/queue.h | 62 +++++++++++ src/server/SilverMUDServer.c | 2 +- src/texteffects.h | 1 + tests/queue-test.c | 132 ++++++++++++++++++++++ 9 files changed, 410 insertions(+), 8 deletions(-) create mode 100644 src/queue.c create mode 100644 src/queue.h create mode 100644 tests/queue-test.c diff --git a/Makefile b/Makefile index 90805f5..7abf4ec 100644 --- a/Makefile +++ b/Makefile @@ -25,5 +25,5 @@ clean: all: clean SilverMUDClient SilverMUDServer all: CFLAGS += -Wall -Wextra -Ofast -debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og +debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og -D debug debug: clean SilverMUDClientDebug SilverMUDServerDebug diff --git a/src/gamelogic.c b/src/gamelogic.c index 58b2d82..eaa0d91 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -136,6 +136,8 @@ int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) // Check that we're not overflowing the queue: if ((queue->currentLength + 1) > MAXQUEUELENGTH) { + // Free the new command, it's getting dumped: + free(newCommand); // Unlock the queue: queue->lock = false; return -1; diff --git a/src/gamelogic.h b/src/gamelogic.h index e916d1b..75eca38 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -1,5 +1,4 @@ -// gamelogic.h: Header file contatning function prototypes and datastructures -// for dealing with the game's logic. +// gamelogic.h: Function prototypes and data-structures for dealing with game logic. // Barry Kane, 2022. #ifndef GAMELOGIC_H #define GAMELOGIC_H @@ -12,7 +11,7 @@ // -=[ Main Game Loop ]=-: // ======================= -// A datastructure containing the needed parameters for a main game loop: +// A data-structure containing the needed parameters for a main game loop: typedef struct gameLogicParameters { int * playerCount; diff --git a/src/linkedlist.h b/src/linkedlist.h index 2f15653..b1cc8c3 100644 --- a/src/linkedlist.h +++ b/src/linkedlist.h @@ -47,9 +47,9 @@ typedef struct list listNode * tail; } list; -// ================== -// -=[ Functions ]=-: -// ================== +// ======================== +// -=[ Functions ]=-: +// ======================== // Allocates and instantiates a list of the specified type: list * createList(listDataType type); diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 0000000..df83496 --- /dev/null +++ b/src/queue.c @@ -0,0 +1,206 @@ +// queue.c: Implements the queue data type and associated functions for SilverMUD. +// Barry Kane, 2022 +#include "queue.h" + +// Allocates and instantiates a queue: +queue * createQueue(void) +{ + // Allocate the memory for the queue: + queue * newQueue = malloc(sizeof(queue)); + + // Instantiate the variables in the data-structure: + newQueue->itemCount = 0; + newQueue->front = NULL; + newQueue->back = NULL; + + // Return the pointer to the new queue: + return newQueue; +} + +// Destroys a queue and all of it's members: +void destroyQueue(queue ** queue) +{ + // Pop everything off of the queue: + while ((*queue)->itemCount > 0) + { + popQueue(*queue); + } + + // Deallocate the queue: + free(*queue); +} + +// Returns the data at the front of the given queue: +queueNode * peekQueue(queue * queue) +{ + return queue->front; +} + +// Removes the current data from the front of the queue: +void popQueue(queue * queue) +{ + // Check if the queue is locked, and wait: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check there is actually anything to remove: + if (queue->itemCount == 0) + { + queue->lock = false; + return; + } + + // Handle the special case of being the last item in the queue: + else if (queue->itemCount == 1) + { + // Deallocate the correct data: + switch (queue->front->type) + { + case EVENT: + { + // TODO: Implement events. + } + case COMMAND: + { + free(queue->front->data.command->command); + free(queue->front->data.command->arguments); + free(queue->front->data.command); + break; + } + case INPUT_MESSAGE: + { + free(queue->front->data.inputMessage->content); + free(queue->front->data.inputMessage); + break; + } + case OUTPUT_MESSAGE: + { + free(queue->front->data.outputMessage->content); + free(queue->front->data.outputMessage); + break; + } + } + + // Deallocate the node: + free(queue->front); + + // Set the correct variables for the queue: + queue->front = NULL; + queue->back = NULL; + queue->itemCount = 0; + + // Unlock the queue: + queue->lock = false; + + return; + } + + // Remove the current front of the queue: + else + { + // Deallocate the correct data: + switch (queue->front->type) + { + case EVENT: + { + // TODO: Implement events. + break; + } + case COMMAND: + { + free(queue->front->data.command->command); + free(queue->front->data.command->arguments); + free(queue->front->data.command); + break; + } + case INPUT_MESSAGE: + { + free(queue->front->data.inputMessage->content); + free(queue->front->data.inputMessage); + break; + } + case OUTPUT_MESSAGE: + { + free(queue->front->data.outputMessage->content); + free(queue->front->data.outputMessage); + break; + } + } + + // Save a pointer to the current node so we don't leak it: + queueNode * nodeToDelete = queue->front; + + // Advance the queue: + queue->front = queue->front->next; + queue->itemCount--; + + // Deallocate the old node: + free(nodeToDelete); + + // Unlock the queue: + queue->lock = false; + + return; + } +} + +// Adds data to the back of a queue: +void pushQueue(queue * queue, void * data, queueDataType type) +{ + // Create a new node: + queueNode * newNode = malloc(sizeof(queueNode)); + + // Copy the data into the correct slot for the type: + switch (type) + { + case EVENT: + { + // TODO: Implement events. + break; + } + case COMMAND: + { + newNode->data.command = data; + break; + } + case INPUT_MESSAGE: + { + newNode->data.inputMessage = data; + break; + } + case OUTPUT_MESSAGE: + { + newNode->data.outputMessage = data; + break; + } + + } + + // Check if the queue is locked: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Set the correct pointers: + newNode->next = NULL; + + if (queue->itemCount == 0) + { + queue->front = newNode; + queue->back = newNode; + } + else + { + queue->back->next = newNode; + queue->back = newNode; + } + + // Increase the queue item count: + queue->itemCount++; + + // Unlock the queue: + queue->lock = false; +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..6bc6afe --- /dev/null +++ b/src/queue.h @@ -0,0 +1,62 @@ +// queue.h: Defines the queue data type and associated function prototypes for SilverMUD. +// Barry Kane, 2022 +#ifndef QUEUE_H +#define QUEUE_H +#include "gamelogic.h" +#include "inputoutput.h" + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +typedef enum queueDataType +{ + EVENT, + COMMAND, + INPUT_MESSAGE, + OUTPUT_MESSAGE +} queueDataType; + +typedef union queueData +{ + outputMessage * outputMessage; + inputMessage * inputMessage; + commandEvent * command; +} queueData; + +typedef struct queueNode queueNode; +typedef struct queueNode +{ + queueDataType type; + queueData data; + queueNode * next; +} queueNode; + +typedef struct queue +{ + bool lock; + size_t itemCount; + queueNode * front; + queueNode * back; +} queue; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Allocates and instantiates a queue: +queue * createQueue(void); + +// Destroys a queue and all of it's members: +void destroyQueue(queue ** queue); + +// Returns the node at the front of the given queue: +queueNode * peekQueue(queue * queue); + +// Removes the current node from the front of the queue: +void popQueue(queue * queue); + +// Adds data to the back of a queue: +void pushQueue(queue * queue, void * data, queueDataType type); + +#endif diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index eddec7d..92e54f6 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -29,7 +29,7 @@ typedef struct sockaddr sockaddr; void sigintHandler(int signal) { printf("Caught signal %d.\n", signal); - exit(EXIT_SUCCESS); + _exit(EXIT_SUCCESS); } int main(int argc, char ** argv) diff --git a/src/texteffects.h b/src/texteffects.h index 060c99c..0d0305b 100644 --- a/src/texteffects.h +++ b/src/texteffects.h @@ -33,4 +33,5 @@ char * logostring = " # # ### ##### ##### ### # # # # #### ### /\n"; void wrapString(char * stringToWrap, int stringLength, int screenWidth); + #endif diff --git a/tests/queue-test.c b/tests/queue-test.c new file mode 100644 index 0000000..d248f9c --- /dev/null +++ b/tests/queue-test.c @@ -0,0 +1,132 @@ +// Test for the queue type in SilverMUD: +#include +#include +#include +#include "../src/queue.h" + +#define formatBoolean(b) ((b) ? "true" : "false") +#define formatEquality(b) ((b) ? "equal" : "not equal") + +int main(void) +{ + // Create a queue: + printf("-=[ Creating queue ]=-:\n"); + queue * testQueue = createQueue(); + + // Check that the queue has the correct values: + printf("-=[ Checking initial values ]=-:\n"); + + printf("- Item count should be 0:\n"); + assert(testQueue->itemCount == 0); + printf("Item count is %d.\n\n", testQueue->itemCount); + + printf("- Lock should be false:\n"); + assert(testQueue->lock == false); + printf("Lock is %s.\n\n", formatBoolean(testQueue->lock)); + + printf("- Front should be (nil):\n"); + assert(testQueue->front == NULL); + printf("Front is %p.\n\n", testQueue->front); + + printf("- Back should be (nil):\n"); + assert(testQueue->back == NULL); + printf("Back is %p.\n\n", testQueue->front); + + // Create some items for the queue: + inputMessage * testInputMessage = malloc(sizeof(inputMessage)); + testInputMessage->sender = NULL; + testInputMessage->next = NULL; + testInputMessage->content = malloc(sizeof(userMessage)); + strcpy(testInputMessage->content->senderName,"Queue Test Input Sender"); + strcpy(testInputMessage->content->messageContent, "Queue Test Input Content - Hello!"); + + outputMessage * testOutputMessage = malloc(sizeof(outputMessage)); + for(int index = 0; index < PLAYERCOUNT; index++) + { + testOutputMessage->targets[index] = NULL; + } + testOutputMessage->next = NULL; + testOutputMessage->content = malloc(sizeof(userMessage)); + strcpy(testOutputMessage->content->senderName, "Queue Test Output Sender"); + strcpy(testOutputMessage->content->messageContent, "Queue Test Output Content - World!"); + + commandEvent * testCommandEvent = malloc(sizeof(commandEvent)); + testCommandEvent->next = NULL; + testCommandEvent->caller = NULL; + testCommandEvent->command = malloc(5 * sizeof(char)); + testCommandEvent->arguments = malloc(15 * sizeof(char)); + strcpy(testCommandEvent->command, "Test"); + strcpy(testCommandEvent->arguments, "Test Arguments"); + + // Add them to the queue: + printf("-=[ Adding items to the queue ]=-:\n"); + printf("- First item, Item count should be 1. Front and Back should be equal.\n"); + pushQueue(testQueue, testInputMessage, INPUT_MESSAGE); + assert(testQueue->itemCount == 1); + assert(testQueue->front == testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Second item, Item count should be 2. Front and Back should be not equal.\n"); + pushQueue(testQueue, testOutputMessage, OUTPUT_MESSAGE); + assert(testQueue->itemCount == 2); + assert(testQueue->front != testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Third item, Item count should be 3. Front and Back should be not equal.\n"); + pushQueue(testQueue, testCommandEvent, COMMAND); + assert(testQueue->itemCount == 3); + assert(testQueue->front != testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("-=[ Checking items and popping from queue ]=-:\n"); + printf("- First item peeked should point to testInputMessage.\n"); + assert(peekQueue(testQueue)->data.inputMessage == testInputMessage); + printf("Peeked data is located at: %p, testInputMessage is located at: %p.\n\n", + peekQueue(testQueue)->data.inputMessage, testInputMessage); + + printf("- Popping first item, Item count should be 2, Front and Back should not be equal.\n"); + popQueue(testQueue); + assert(testQueue->itemCount == 2); + assert(testQueue->front != testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Second item peeked should point to testOutputMessage.\n"); + assert(peekQueue(testQueue)->data.outputMessage == testOutputMessage); + printf("Peeked data is located at: %p, testOutputMessage is located at: %p.\n\n", + peekQueue(testQueue)->data.outputMessage, testOutputMessage); + + printf("- Popping second item, Item count should be 1, Front and Back should be equal.\n"); + popQueue(testQueue); + assert(testQueue->itemCount == 1); + assert(testQueue->front == testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Third item peeked should point to testCommandEvent.\n"); + assert(peekQueue(testQueue)->data.command == testCommandEvent); + printf("Peeked data is located at: %p, testCommandEvent is located at: %p.\n\n", + peekQueue(testQueue)->data.command, testCommandEvent); + + printf("- Popping third item:\n"); + popQueue(testQueue); + + printf("- Item count should be 0:\n"); + assert(testQueue->itemCount == 0); + printf("Item count is %d.\n\n", testQueue->itemCount); + + printf("- Lock should be false:\n"); + assert(testQueue->lock == false); + printf("Lock is %s.\n\n", formatBoolean(testQueue->lock)); + + printf("- Front should be (nil):\n"); + assert(testQueue->front == NULL); + printf("Front is %p.\n\n", testQueue->front); + + printf("- Back should be (nil):\n"); + assert(testQueue->back == NULL); + printf("Back is %p.\n\n", testQueue->front); +} From 9b3df5928b45ba2eb74c27f25ee71fa1813fa0af Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Mon, 19 Dec 2022 23:44:16 +0000 Subject: [PATCH 33/53] Refactored the input queue to use the new queue type - Removed all traces of the previous inputMessageQueue type. - Removed the pointer "next" in inputMessage. - Rewrote the the main thread and game logic thread to use a queue for input. --- src/gamelogic.c | 17 +++--- src/gamelogic.h | 6 +- src/inputoutput.c | 112 ----------------------------------- src/inputoutput.h | 22 ------- src/server/SilverMUDServer.c | 29 +++++++-- 5 files changed, 37 insertions(+), 149 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index eaa0d91..698a917 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -5,6 +5,7 @@ #include #include #include +#include "queue.h" #include "constants.h" #include "gamelogic.h" #include "playerdata.h" @@ -28,25 +29,21 @@ void * gameLogicLoop(void * parameters) { evaluateNextCommand(threadParameters, commandQueue); } + // Check for new messages and pop them off the queue: - if(threadParameters->inputQueue->currentLength != 0) + if(threadParameters->inputQueue->itemCount != 0) { while(threadParameters->inputQueue->lock == true); threadParameters->inputQueue->lock = true; - currentInput = peekInputMessage(threadParameters->inputQueue); + currentInput = peekQueue(threadParameters->inputQueue)->data.inputMessage; userInputSanatize(currentInput->content->messageContent, MAX); // A slash as the first character means the message is a user command: if(currentInput->content->messageContent[0] == '/') { queueMessagedCommand(commandQueue, currentInput); } - else if(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area) - { - currentInput = NULL; - threadParameters->inputQueue->lock = false; - dequeueInputMessage(threadParameters->inputQueue); - } - else + + else if (!(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area)) { strncpy(currentInput->content->senderName, currentInput->sender->playerName, 32); // Create an array of players in the same area to receive the message: @@ -72,7 +69,7 @@ void * gameLogicLoop(void * parameters) } currentInput = NULL; threadParameters->inputQueue->lock = false; - dequeueInputMessage(threadParameters->inputQueue); + popQueue(threadParameters->inputQueue); } } pthread_exit(NULL); diff --git a/src/gamelogic.h b/src/gamelogic.h index 75eca38..e1da35c 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -2,11 +2,15 @@ // Barry Kane, 2022. #ifndef GAMELOGIC_H #define GAMELOGIC_H +#include "queue.h" #include "areadata.h" #include "constants.h" #include "playerdata.h" #include "inputoutput.h" +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + // ======================= // -=[ Main Game Loop ]=-: // ======================= @@ -17,7 +21,7 @@ typedef struct gameLogicParameters int * playerCount; list * areaList; playerInfo * connectedPlayers; - inputMessageQueue * inputQueue; + queue * inputQueue; outputMessageQueue * outputQueue; list * globalSkillList; } gameLogicParameters; diff --git a/src/inputoutput.c b/src/inputoutput.c index 61da91e..b780ae4 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -212,112 +212,6 @@ int dequeueOutputMessage(outputMessageQueue * queue) } } -inputMessageQueue * createInputMessageQueue(void) -{ - inputMessageQueue * newQueue = malloc(sizeof(inputMessageQueue)); - newQueue->front = NULL; - newQueue->back = NULL; - newQueue->currentLength = 0; - newQueue->lock = false; - return newQueue; -} - -int dequeueInputMessage(inputMessageQueue * queue) -{ - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check the list isn't empty: - if(queue->front == NULL) - { - queue->lock = false; - return -1; - } - - // If there is only one item in the queue: - else if(queue->front == queue->back) - { - free(queue->front->content); - free(queue->front); - queue->front = NULL; - queue->back = NULL; - queue->currentLength--; - queue->lock = false; - return 0; - } - - // Remove the front item: - else - { - inputMessage * messageToDelete = queue->front; - queue->front = queue->front->next; - free(messageToDelete->content); - free(messageToDelete); - queue->currentLength--; - queue->lock = false; - return 0; - } -} - -int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer) -{ - // Copy the message into a new input message: - inputMessage * input = malloc(sizeof(inputMessage)); - - // Allocate the internal userMessage to store the message: - input->content = malloc(sizeof(userMessage)); - - // Copy the userMessage to the internal userMessage: - strncpy(input->content->senderName, messageToQueue.senderName, 32); - strncpy(input->content->messageContent, messageToQueue.messageContent, MAX); - - // We have no targets, NULL sends to all players in an area: - input->sender = sendingPlayer; - - // 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 = input; - queue->back = input; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - else - { - queue->back->next = input; - queue->back = input; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - } -} - void userInputSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) @@ -345,12 +239,6 @@ void userNameSanatize(char * inputString, int length) inputString[length - 1] = '\0'; } -// Return the front inputMessage from an inputMessageQueue: -inputMessage * peekInputMessage(inputMessageQueue * queue) -{ - return queue->front; -} - outputMessage * peekOutputMessage(outputMessageQueue * queue) { return queue->front; diff --git a/src/inputoutput.h b/src/inputoutput.h index 177fac0..1fea866 100644 --- a/src/inputoutput.h +++ b/src/inputoutput.h @@ -67,32 +67,10 @@ outputMessage * peekOutputMessage(outputMessageQueue * queue); typedef struct inputMessage inputMessage; typedef struct inputMessage { - inputMessage * next; playerInfo * sender; userMessage * content; } inputMessage; -// A first-in first-out queue for message input from players: -typedef struct inputMessageQueue -{ - bool lock; - int currentLength; - inputMessage * back; - inputMessage * front; -} inputMessageQueue; - -// Create a inputMessageQueue: -inputMessageQueue * createInputMessageQueue(void); - -// Enqueue a userMessage to an inputMessageQueue: -int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer); - -// Dequeue the front inputMessage from an inputMessageQueue: -int dequeueInputMessage(inputMessageQueue * queue); - -// Return the front inputMessage from an inputMessageQueue: -inputMessage * peekInputMessage(inputMessageQueue * queue); - // ======================= // -=[Input Sanitation]=-: // ======================= diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 92e54f6..2b9613c 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -17,6 +17,7 @@ #include #include +#include "../queue.h" #include "../areadata.h" #include "../gamelogic.h" #include "../constants.h" @@ -45,7 +46,7 @@ int main(int argc, char ** argv) playerInfo connectedPlayers[PLAYERCOUNT]; char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; - inputMessageQueue * inputQueue = createInputMessageQueue(); + queue * inputQueue = createQueue(); outputMessageQueue * outputQueue = createOutputMessageQueue(); // Parse command-line options: @@ -215,7 +216,7 @@ int main(int argc, char ** argv) while(true) { - // Clear the set of file descriptors angad add the master socket: + // Clear the set of file descriptors and add the master socket: FD_ZERO(&connectedClients); FD_SET(socketFileDesc, &connectedClients); clientsAmount = socketFileDesc; @@ -275,7 +276,18 @@ int main(int argc, char ** argv) strcpy(sendBuffer.messageContent, "Welcome to the server!"); messageSend(tlssessions[index], &sendBuffer); strcpy(receiveBuffer.messageContent, "/look"); - queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); + + // Allocate the memory for a new input message: + inputMessage * newMessage = malloc(sizeof(inputMessage)); + newMessage->content = malloc(sizeof(userMessage)); + + // Copy in the correct data: + memcpy(newMessage->content, &receiveBuffer, sizeof(userMessage)); + newMessage->sender = &connectedPlayers[index]; + + // Push the new message onto the queue: + pushQueue(inputQueue, newMessage, INPUT_MESSAGE); + break; } } @@ -315,7 +327,16 @@ int main(int argc, char ** argv) // Otherwise, they've sent a message: else { - queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); + // Allocate the memory for a new input message: + inputMessage * newMessage = malloc(sizeof(inputMessage)); + newMessage->content = malloc(sizeof(userMessage)); + + // Copy in the correct data: + memcpy(newMessage->content, &receiveBuffer, sizeof(userMessage)); + newMessage->sender = &connectedPlayers[index]; + + // Push the new message onto the queue: + pushQueue(inputQueue, newMessage, INPUT_MESSAGE); } } } From 15d82f59ee96cca51bfc9960c77c46ff59a19011 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Tue, 20 Dec 2022 15:55:24 +0000 Subject: [PATCH 34/53] Refactored the outputQueue to use the new queue type - Patched a bug where I didn't set the correct type for queue nodes. - Removed all traces of the previous outputMessageQueue type. - Removed the pointer "next" in outputMessage. - Rewrote the the main thread and game logic thread to use a queue for output. - Refactored outputMessage to allow for a variable amount of playerInfo pointers. --- src/gamelogic.c | 134 ++++++++++++++++++++------- src/gamelogic.h | 9 +- src/inputoutput.c | 173 ++--------------------------------- src/inputoutput.h | 28 +----- src/queue.c | 2 +- src/server/SilverMUDServer.c | 24 +++-- 6 files changed, 137 insertions(+), 233 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index 698a917..b3ef2a4 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -43,28 +43,40 @@ void * gameLogicLoop(void * parameters) queueMessagedCommand(commandQueue, currentInput); } - else if (!(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area)) + else if (!(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area) && + currentInput->content->messageContent[0] != '\n') { + // Copy the correct name into the sender name field: 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++) + currentInput->content->senderName[31] = '\0'; + + // Allocate an array of playerInfo to store the current players in the area for the output message: + playerInfo ** recipients = malloc(sizeof(playerInfo*) * PLAYERCOUNT); + + // Initialize them all to NULL: + for (int index = 0; index < PLAYERCOUNT; index++) { recipients[index] = NULL; } - int recipientCount = 0; - for(int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++) + + // Get the players in the current area and add them to our array: + int recipientIndex = 0; + for (int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++) { - if(threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea) + if (threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea) { - recipients[recipientCount] = &threadParameters->connectedPlayers[playerIndex]; - recipientCount++; + recipients[recipientIndex] = &threadParameters->connectedPlayers[playerIndex]; + recipientIndex++; } } - if(currentInput->content->messageContent[0] != '\n') - { - queueTargetedOutputMessage(threadParameters->outputQueue, currentInput->content, recipients, recipientCount); - } + + // Create the outputMessage for the queue: + outputMessage * newOutputMessage = createTargetedOutputMessage(currentInput->content, recipients, recipientIndex); + + // Push the message onto the queue: + pushQueue(threadParameters->outputQueue, newOutputMessage, OUTPUT_MESSAGE); + + // Free the array; free(recipients); } currentInput = NULL; @@ -320,16 +332,31 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) break; } } - queueTargetedOutputMessage(parameters->outputQueue, tryMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, tryOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage: free(tryMessage); } // Exit command: Sends an "empty" exit message to disconnect a client: if(strncmp(currentCommand->command, "exit", 4) == 0) { + // Allocate a userMessage containing null characters as the first char in both fields: userMessage * exitMessage = malloc(sizeof(userMessage)); exitMessage->senderName[0] = '\0'; exitMessage->messageContent[0] = '\0'; - queueTargetedOutputMessage(parameters->outputQueue, exitMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * exitOutputMessage = createTargetedOutputMessage(exitMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, exitOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage free(exitMessage); } @@ -362,7 +389,14 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33); strncat(lookMessage->messageContent, "\n", 2); strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35); - queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); + + //queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); bzero(lookMessage, sizeof(userMessage)); // Loop through the paths and send the appropriate amount of messages: @@ -375,7 +409,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { if((charCount + 64) >= MAX) { - queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); + bzero(lookMessage, sizeof(userMessage)); charCount = 0; } @@ -384,7 +422,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(lookMessage->messageContent, formattedString, 64); charCount += 64; } - queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + // Allocate another outputMessage for the queue: + lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); } free(lookMessage); } @@ -423,14 +465,6 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { // TODO: Implement. } - if(strncmp(currentCommand->command, "skillissue", 10) == 0) - { - userMessage * statMessage = calloc(1, sizeof(userMessage)); - statMessage->senderName[0] = '\0'; - strcpy(statMessage->messageContent, "Have you tried getting good?"); - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); - free(statMessage); - } // Stat command: Displays the current character's sheet. if(strncmp(currentCommand->command, "stat", 4) == 0) { @@ -462,8 +496,13 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience); } strncat(statMessage->messageContent, formattedString, 120); + + // Allocate an outputMessage for the queue: + outputMessage * statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); bzero(statMessage->messageContent, sizeof(char) * MAX); if(currentCommand->caller->skills->head != NULL) { @@ -480,8 +519,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(statMessage->messageContent, formattedString, 120); if((charCount + 43) >= MAX) { -// strncat(statMessage->messageContent, "\n", 2); - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); bzero(statMessage, sizeof(userMessage)); charCount = 0; break; @@ -497,7 +539,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) addNewline = true; } } - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); } free(statMessage); free(formattedString); @@ -570,8 +616,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(specMessage->messageContent, "You have no spec points available.", 35); } - // Send the message: - queueTargetedOutputMessage(parameters->outputQueue, specMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, specOutputMessage, OUTPUT_MESSAGE); // Show the new stat sheet: queue->lock = false; @@ -611,7 +660,13 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n"); } - queueTargetedOutputMessage(parameters->outputQueue, skillMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * skillOutputMessage = createTargetedOutputMessage(skillMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, skillOutputMessage, OUTPUT_MESSAGE); + free(skillMessage); } if(strncmp(currentCommand->command, "listskills", 10) == 0) @@ -630,7 +685,12 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(listMessage->messageContent, formattedString, 120); if((charCount + 46) >= MAX) { - queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); + bzero(listMessage, sizeof(userMessage)); charCount = 0; addNewline = false; @@ -647,7 +707,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) } skillIndex++; } - queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); free(listMessage); free(formattedString); } diff --git a/src/gamelogic.h b/src/gamelogic.h index e1da35c..97d7d76 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -18,11 +18,16 @@ typedef struct queue queue; // A data-structure containing the needed parameters for a main game loop: typedef struct gameLogicParameters { + // Players: int * playerCount; - list * areaList; playerInfo * connectedPlayers; + + // Queues: queue * inputQueue; - outputMessageQueue * outputQueue; + queue * outputQueue; + + // Lists: + list * areaList; list * globalSkillList; } gameLogicParameters; diff --git a/src/inputoutput.c b/src/inputoutput.c index b780ae4..93912d6 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -46,170 +46,22 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM return returnValue; } -outputMessageQueue * createOutputMessageQueue(void) +outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount) { - outputMessageQueue * newQueue = malloc(sizeof(outputMessageQueue)); - newQueue->front = NULL; - newQueue->back = NULL; - newQueue->currentLength = 0; - newQueue->lock = false; - return newQueue; -} - -int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) -{ - // Copy the message into a new output message: + // Allocate a new output message: outputMessage * newOutputMessage = malloc(sizeof(outputMessage)); - - // Allocate the internal userMessage to store the message: - newOutputMessage->content = malloc(sizeof(userMessage)); - - // Copy the userMessage to the internal userMessage: - 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: - newOutputMessage->targets[0] = NULL; - - // 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; - } - } -} - -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); + // Allocate an array of playerInfo for the output message recepients: + newOutputMessage->recipients = malloc(sizeof(playerInfo*) * recipientsCount); - // Lock the queue: - queue->lock = true; + // Copy in the appropriate data: + memcpy(newOutputMessage->recipients, recipients, sizeof(playerInfo *) * recipientsCount); + memcpy(newOutputMessage->content, messageToQueue, sizeof(userMessage)); + newOutputMessage->recipientsCount = recipientsCount; - // 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; - } - } -} - -int dequeueOutputMessage(outputMessageQueue * queue) -{ - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check the list isn't empty: - if(queue->front == NULL) - { - queue->lock = false; - return -1; - } - - // If there is only one item in the queue: - else if(queue->front == queue->back) - { - free(queue->front->content); - free(queue->front); - queue->front = NULL; - queue->back = NULL; - queue->currentLength--; - queue->lock = false; - return 0; - } - - // Remove the front item: - else - { - outputMessage * messageToDelete = queue->front; - queue->front = queue->front->next; - free(messageToDelete->content); - free(messageToDelete); - queue->currentLength--; - queue->lock = false; - return 0; - } + // Return a pointer to the new outputMessage: + return newOutputMessage; } void userInputSanatize(char * inputString, int length) @@ -238,8 +90,3 @@ void userNameSanatize(char * inputString, int length) } inputString[length - 1] = '\0'; } - -outputMessage * peekOutputMessage(outputMessageQueue * queue) -{ - return queue->front; -} diff --git a/src/inputoutput.h b/src/inputoutput.h index 1fea866..72ad893 100644 --- a/src/inputoutput.h +++ b/src/inputoutput.h @@ -33,33 +33,13 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM typedef struct outputMessage outputMessage; typedef struct outputMessage { - outputMessage * next; - playerInfo * targets[PLAYERCOUNT]; + int recipientsCount; userMessage * content; + playerInfo ** recipients; } outputMessage; -// A first-in first-out queue for message output to players: -typedef struct outputMessageQueue -{ - bool lock; - int currentLength; - outputMessage * back; - outputMessage * front; -} outputMessageQueue; - -// Creates and initializes a outputMessageQueue: -outputMessageQueue * createOutputMessageQueue(void); - -// Enqueue a userMessage to an outputMessageQueue: -int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue); -int queueTargetedOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue, - playerInfo ** targets, int numberOfTargets); - -// Dequeue the front outputMessage from an outputMessageQueue: -int dequeueOutputMessage(outputMessageQueue * queue); - -// Return the front outputMessage from an outputMessageQueue: -outputMessage * peekOutputMessage(outputMessageQueue * queue); +// Create a targetedOutput message to be delivered to the players pointed to in recipients: +outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount); // ================== // -=[Input Queue]=-: diff --git a/src/queue.c b/src/queue.c index df83496..22fc1bf 100644 --- a/src/queue.c +++ b/src/queue.c @@ -151,7 +151,7 @@ void pushQueue(queue * queue, void * data, queueDataType type) { // Create a new node: queueNode * newNode = malloc(sizeof(queueNode)); - + newNode->type = type; // Copy the data into the correct slot for the type: switch (type) { diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 2b9613c..adf6a60 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -46,8 +46,7 @@ int main(int argc, char ** argv) playerInfo connectedPlayers[PLAYERCOUNT]; char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; - queue * inputQueue = createQueue(); - outputMessageQueue * outputQueue = createOutputMessageQueue(); + queue * inputQueue = createQueue(), * outputQueue = createQueue(); // Parse command-line options: int currentopt = 0; @@ -343,38 +342,47 @@ int main(int argc, char ** argv) } // Run through the output queue and send all unused messages: - while(outputQueue->currentLength != 0) + while(outputQueue->itemCount != 0) { + // Wait until the queue unlocks: while(outputQueue->lock); + + // Lock the queue: outputQueue->lock = true; - outputMessage * message = peekOutputMessage(outputQueue); + + // Get a message off the queue: + outputMessage * message = peekQueue(outputQueue)->data.outputMessage; + + // Unlock the queue: outputQueue->lock = false; // If the first target is set to NULL, it's intended for all connected: - if(message->targets[0] == NULL) + if(message->recipientsCount == 0) { for (int index = 0; index < PLAYERCOUNT; index++) { messageSend(tlssessions[index], message->content); } } + // Otherwise, send it only to the targeted players: else { int targetIndex = 0; for(int index = 0; index < PLAYERCOUNT; index++) { - if(message->targets[targetIndex] == NULL) + if(targetIndex == message->recipientsCount) { break; } - if(&connectedPlayers[index] == message->targets[targetIndex]) + if(&connectedPlayers[index] == message->recipients[targetIndex]) { targetIndex++; messageSend(tlssessions[index], message->content); } } } - dequeueOutputMessage(outputQueue); + // Remove the output message from the queue: + popQueue(outputQueue); } } pthread_cancel(gameLogicThread); From 6a653c75b98e59f03909da05711a09b834311f01 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 21 Dec 2022 00:49:26 +0000 Subject: [PATCH 35/53] Implemented proper thread sleeping and additional output thread - Replaced previous inefficient "spin-waiting" with proper thread sleeping. - Added threading primitives to the queue type to enable this. - Added additional thread for output management. - Miscellanous cleanup and restructuring. --- src/client/SilverMUDClient.c | 3 +- src/gamelogic.c | 10 ++++- src/gamelogic.h | 63 +++++++++++++++--------------- src/inputoutput.c | 70 +++++++++++++++++++++++++++++++++ src/inputoutput.h | 74 ++++++++++++++++++++--------------- src/queue.c | 15 ++++++- src/queue.h | 5 +++ src/server/SilverMUDServer.c | 76 +++++++++--------------------------- 8 files changed, 191 insertions(+), 125 deletions(-) diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index be8b3da..df6ac0a 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -14,6 +14,7 @@ #include #include +#include "../queue.h" #include "../constants.h" #include "../playerdata.h" #include "../texteffects.h" @@ -225,7 +226,7 @@ int main(int argc, char ** argv) serverAddress.sin_port = htons(port); // Connect the server and client sockets, Kronk: - if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) + if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) { slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay); exit(0); diff --git a/src/gamelogic.c b/src/gamelogic.c index b3ef2a4..60b3e85 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -17,7 +17,7 @@ // ======================= // Thread function which runs the main game loop, given the needed parameters: -void * gameLogicLoop(void * parameters) +void * gameLogicHandler(void * parameters) { gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; @@ -25,11 +25,17 @@ void * gameLogicLoop(void * parameters) while(true) { // Evaluate remaining commands: - if(commandQueue->currentLength != 0) + while(commandQueue->currentLength != 0) { evaluateNextCommand(threadParameters, commandQueue); } + // Wait if there is nothing to do: + if(threadParameters->inputQueue->itemCount == 0) + { + pthread_cond_wait(&threadParameters->inputQueue->condition, &threadParameters->inputQueue->mutex); + } + // Check for new messages and pop them off the queue: if(threadParameters->inputQueue->itemCount != 0) { diff --git a/src/gamelogic.h b/src/gamelogic.h index 97d7d76..631ef6b 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -8,35 +8,10 @@ #include "playerdata.h" #include "inputoutput.h" -// Let the compiler know there will be structs defined elsewhere: -typedef struct queue queue; +// ======================== +// -=[ Data Structures ]=-: +// ======================== -// ======================= -// -=[ Main Game Loop ]=-: -// ======================= - -// A data-structure containing the needed parameters for a main game loop: -typedef struct gameLogicParameters -{ - // Players: - int * playerCount; - playerInfo * connectedPlayers; - - // Queues: - queue * inputQueue; - queue * outputQueue; - - // Lists: - list * areaList; - list * globalSkillList; -} gameLogicParameters; - -// Thread function which runs the main game loop, given the needed parameters: -void * gameLogicLoop(void * parameters); - -// ====================== -// -=[ Command Queue ]=-: -// ====================== typedef struct commandEvent commandEvent; typedef struct commandEvent { @@ -56,6 +31,32 @@ typedef struct commandQueue commandEvent * front; } commandQueue; +// A data-structure containing the needed parameters for a main game loop: +typedef struct gameLogicParameters +{ + // Players: + int * playerCount; + playerInfo * connectedPlayers; + + // Queues: + queue * inputQueue; + queue * outputQueue; + + // Lists: + list * areaList; + list * globalSkillList; +} gameLogicParameters; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Player movement: +int movePlayerToArea(playerInfo * player, char * requestedPath); + +// Thread function which runs the main game loop, given the needed parameters: +void * gameLogicHandler(void * parameters); + // Create a commandQueue: commandQueue * createCommandQueue(void); @@ -82,9 +83,6 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); // -=[ Gameplay Primitives ]=-: // ============================ -// Player movement: -int movePlayerToArea(playerInfo * player, char * requestedPath); - typedef enum outcome { CRITICAL_FAILURE, @@ -94,6 +92,9 @@ typedef enum outcome ERROR } outcome; +// Player movement: +int movePlayerToArea(playerInfo * player, char * requestedPath); + // Run a stat check: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); diff --git a/src/inputoutput.c b/src/inputoutput.c index 93912d6..4bd4b10 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -5,7 +5,10 @@ #include #include #include +#include #include + +#include "queue.h" #include "constants.h" #include "playerdata.h" #include "inputoutput.h" @@ -46,6 +49,7 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM return returnValue; } +// Allocate and initialize an outputMessage targeted to a variable amount of players: outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount) { // Allocate a new output message: @@ -64,10 +68,72 @@ outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, player return newOutputMessage; } +// A function for the output thread, which sends queued messages: +void * outputThreadHandler(void * parameters) +{ + outputThreadParameters * variables = parameters; + queue * outputQueue = variables->outputQueue; + gnutls_session_t * tlssessions = variables->tlssessions; + playerInfo * connectedPlayers = variables->connectedPlayers; + + while(true) + { + if(outputQueue->itemCount == 0) + { + pthread_cond_wait(&outputQueue->condition, &outputQueue->mutex); + } + // Run through the output queue and send all unused messages: + while(outputQueue->itemCount != 0) + { + // Wait until the queue unlocks: + while(outputQueue->lock); + + // Lock the queue: + outputQueue->lock = true; + + // Get a message off the queue: + outputMessage * message = peekQueue(outputQueue)->data.outputMessage; + + // Unlock the queue: + outputQueue->lock = false; + + // If the first target is set to NULL, it's intended for all connected: + if(message->recipientsCount == 0) + { + for (int index = 0; index < PLAYERCOUNT; index++) + { + messageSend(tlssessions[index], message->content); + } + } + // Otherwise, send it only to the targeted players: + else + { + int targetIndex = 0; + for(int index = 0; index < PLAYERCOUNT; index++) + { + if(targetIndex == message->recipientsCount) + { + break; + } + if(&connectedPlayers[index] == message->recipients[targetIndex]) + { + targetIndex++; + messageSend(tlssessions[index], message->content); + } + } + } + // Remove the output message from the queue: + popQueue(outputQueue); + } + } +} + +// Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) { + // If it's not a printable character, it has no buisness being here: if(!isprint(inputString[index])) { inputString[index] = '\n'; @@ -75,18 +141,22 @@ void userInputSanatize(char * inputString, int length) break; } } + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } +// Sanatize user names so they display correctly; void userNameSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) { + // If it's not a printable character, it has no buisness being here: if(!isprint(inputString[index])) { inputString[index] = '\0'; break; } } + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } diff --git a/src/inputoutput.h b/src/inputoutput.h index 72ad893..f554832 100644 --- a/src/inputoutput.h +++ b/src/inputoutput.h @@ -4,33 +4,36 @@ #ifndef INPUTOUTPUT_H #define INPUTOUTPUT_H #include -#include #include -#include "constants.h" -#include "playerdata.h" +#include #include -// A message datastructure containing a user/character name and the content: +#include "queue.h" +#include "constants.h" +#include "playerdata.h" + +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +// Contains a character/player name and the content of a message: typedef struct userMessage { char senderName[32]; char messageContent[MAX]; } userMessage; -// ================== -// -=[Message I/O]=-: -// ================== +// Contains a userMessage and a pointer to the playerInfo of the connection which sent it: +typedef struct inputMessage +{ + playerInfo * sender; + userMessage * content; +} inputMessage; -// Sends a message to a given TLS session, wraps the calls to gnutls_write: -int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); - -// Receives a message from a given TLS session, wraps the calls to gnutls_read: -int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); - -// =================== -// -=[Output Queue]=-: -// =================== -typedef struct outputMessage outputMessage; +// Contains a userMessage and pointers to the playerInfo of the recipients and the number of them: typedef struct outputMessage { int recipientsCount; @@ -38,27 +41,34 @@ typedef struct outputMessage playerInfo ** recipients; } outputMessage; +// Contains the relevant parameters for the outputThreadLoop function: +typedef struct outputThreadParameters +{ + queue * outputQueue; + gnutls_session_t * tlssessions; + playerInfo * connectedPlayers; +} outputThreadParameters; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Sends a message to a given TLS session, wraps the calls to gnutls_write: +int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); + +// Receives a message from a given TLS session, wraps the calls to gnutls_read: +int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); + // Create a targetedOutput message to be delivered to the players pointed to in recipients: outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount); -// ================== -// -=[Input Queue]=-: -// ================== -typedef struct inputMessage inputMessage; -typedef struct inputMessage -{ - playerInfo * sender; - userMessage * content; -} inputMessage; +// A function for the output thread, which sends queued messages: +void * outputThreadHandler(void * parameters); -// ======================= -// -=[Input Sanitation]=-: -// ======================= - -// Sanatize user input to ensure it's okay to send to the server: +// Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length); -// Sanatize user names so they display correctly; +// Sanatize user names so they display correctly: void userNameSanatize(char * inputString, int length); #endif diff --git a/src/queue.c b/src/queue.c index 22fc1bf..d137525 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,5 +1,6 @@ // queue.c: Implements the queue data type and associated functions for SilverMUD. // Barry Kane, 2022 +#include #include "queue.h" // Allocates and instantiates a queue: @@ -13,6 +14,10 @@ queue * createQueue(void) newQueue->front = NULL; newQueue->back = NULL; + // Create the threading constructs: + newQueue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + newQueue->condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + // Return the pointer to the new queue: return newQueue; } @@ -27,7 +32,10 @@ void destroyQueue(queue ** queue) } // Deallocate the queue: - free(*queue); + free(*queue); + + // Point the queue pointer to NULL; + *queue = NULL; } // Returns the data at the front of the given queue: @@ -41,7 +49,7 @@ void popQueue(queue * queue) { // Check if the queue is locked, and wait: while (queue->lock); - + // Lock the queue: queue->lock = true; @@ -203,4 +211,7 @@ void pushQueue(queue * queue, void * data, queueDataType type) // Unlock the queue: queue->lock = false; + + // Flag that the queue was modified: + pthread_cond_broadcast(&queue->condition); } diff --git a/src/queue.h b/src/queue.h index 6bc6afe..c2691df 100644 --- a/src/queue.h +++ b/src/queue.h @@ -9,6 +9,9 @@ // -=[ Data Structures ]=-: // ======================== +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + typedef enum queueDataType { EVENT, @@ -38,6 +41,8 @@ typedef struct queue size_t itemCount; queueNode * front; queueNode * back; + pthread_mutex_t mutex; + pthread_cond_t condition; } queue; // ======================== diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index adf6a60..fe63799 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -40,7 +40,7 @@ int main(int argc, char ** argv) int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; - pthread_t gameLogicThread; + pthread_t gameLogicThread, outputThread; int clientSockets[PLAYERCOUNT]; userMessage sendBuffer, receiveBuffer; playerInfo connectedPlayers[PLAYERCOUNT]; @@ -54,11 +54,11 @@ int main(int argc, char ** argv) { switch(currentopt) { - case 'd': - { - delay = atoi(optarg); - break; - } + case 'd': + { + delay = atoi(optarg); + break; + } } } @@ -146,7 +146,6 @@ int main(int argc, char ** argv) slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay); } - // bzero(&serverAddress, sizeof(serverAddress)); // Assign IP and port: @@ -207,11 +206,19 @@ int main(int argc, char ** argv) gameLogicThreadParameters->outputQueue = outputQueue; gameLogicThreadParameters->inputQueue = inputQueue; gameLogicThreadParameters->areaList = areas; - pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters); + pthread_create(&gameLogicThread, NULL, &gameLogicHandler, gameLogicThreadParameters); slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); - slowPrint("=====\n", delay); - struct timeval timeout = {0, 500}; + + // Prepare the output queue thread: + outputThreadParameters * outputParameters = malloc(sizeof(outputThreadParameters)); + outputParameters->outputQueue = outputQueue; + outputParameters->tlssessions = tlssessions; + outputParameters->connectedPlayers = connectedPlayers; + pthread_create(&outputThread, NULL, &outputThreadHandler, outputParameters); + + slowPrint("\tOutput Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); + slowPrint("=====\n", delay); while(true) { @@ -239,7 +246,7 @@ int main(int argc, char ** argv) } // See if a connection is ready to be interacted with: - activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout); + activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); // Check if select() worked: if ((activityCheck < 0) && (errno != EINTR)) @@ -286,7 +293,6 @@ int main(int argc, char ** argv) // Push the new message onto the queue: pushQueue(inputQueue, newMessage, INPUT_MESSAGE); - break; } } @@ -340,52 +346,8 @@ int main(int argc, char ** argv) } } } - - // Run through the output queue and send all unused messages: - while(outputQueue->itemCount != 0) - { - // Wait until the queue unlocks: - while(outputQueue->lock); - - // Lock the queue: - outputQueue->lock = true; - - // Get a message off the queue: - outputMessage * message = peekQueue(outputQueue)->data.outputMessage; - - // Unlock the queue: - outputQueue->lock = false; - - // If the first target is set to NULL, it's intended for all connected: - if(message->recipientsCount == 0) - { - for (int index = 0; index < PLAYERCOUNT; index++) - { - messageSend(tlssessions[index], message->content); - } - } - // Otherwise, send it only to the targeted players: - else - { - int targetIndex = 0; - for(int index = 0; index < PLAYERCOUNT; index++) - { - if(targetIndex == message->recipientsCount) - { - break; - } - if(&connectedPlayers[index] == message->recipients[targetIndex]) - { - targetIndex++; - messageSend(tlssessions[index], message->content); - } - } - } - // Remove the output message from the queue: - popQueue(outputQueue); - } } pthread_cancel(gameLogicThread); + pthread_cancel(outputThread); exit(EXIT_SUCCESS); } - From c2c77d634343a66b2d7a54e96d4908e9b436f91e Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 21 Dec 2022 20:31:32 +0000 Subject: [PATCH 36/53] Cleaned and styled SilverMUDClient.c - Brought SilverMUDClient.c to a consistent style. - Cleaned and neatened various parts of SilverMUDClient.c. - Minor cleanup of playerdata.h. - Began writing the SilverMUD Style Guide. - Removed outputQueue-test.c, because outputQueue no longer exists. --- SilverMUD.org | 65 ++++++++++--- src/client/SilverMUDClient.c | 181 ++++++++++++++++++++--------------- src/playerdata.h | 4 +- tests/outputQueue-test.c | 35 ------- 4 files changed, 156 insertions(+), 129 deletions(-) delete mode 100644 tests/outputQueue-test.c diff --git a/SilverMUD.org b/SilverMUD.org index 070bfe8..0c748ff 100644 --- a/SilverMUD.org +++ b/SilverMUD.org @@ -1,28 +1,65 @@ +#+LATEX_HEADER: \RequirePackage[left=0.3in,top=0.3in,right=0.3in,bottom=0.3in, a4paper]{geometry} * SilverMUD: The Hackable Terminal-Top Roleplaying Game. SilverMUD is a tool for creating engaging and communal stories, all over the world through the internet. It's designed to give a gamemaster the same power to improvise that they have at the table, through simple programming and easy-to-understand structures. -** Player's Guide -*** The Basic Commands + +* Player's Guide +** The Basic Commands SilverMUD is played through a set of very simple commands. To use a command, type a forward-slash (/) followed immediately by the command name. The command can be upper or lower-case. -| Command | Arguments | Effect | -|---------+---------------------------------------------------+--------------------------------------------------------------------| -| JOIN | Takes a character name | Logs you into the server with the given character name. | -| MOVE | Takes a path name or a path number | Moves you down the given path. | -| LOOK | None | Gives you a description of what's around you, and what you can do. | -| STAT | None | Displays your current status and character sheet. | -| SPEC | Core stat name | Allows you to apply spec points to a given stat. | -| TRY | Core stat name or skill name and an object number | Attempt to use the given stat or skill on the object. | +| Command | Arguments | Effect | +|---------+------------------------------------------+---------------------------------------------------------| +| JOIN | Character Name | Logs you into the server with the given character name. | +| MOVE | Path Name/Path Number | Moves you down the given path. | +| LOOK | None | Describes the current area. | +| STAT | None | Displays your current status and character sheet. | +| SPEC | Core Stat Name | Allows you to apply spec points to a given stat. | +| TRY | Core Stat Name/Skill Name, Object Number | Attempt to use the given stat or skill on the object. | -** Gamemaster's Guide -*** Running the Server: +* Gamemaster's Guide +** Running the Server: -** Developer's Guide -*** Build Prerequisites: +* Developer's Guide +** Build Prerequisites: SilverMUD has the following dependencies: - GnuTLS - ncurses + +** C Style Guide: +These rules attempt to make the program as visually clear as possible, while +some rules may be made based on my own personal tastes. + +- () :: These are parentheses. +- [] :: These are brackets. +- {} :: These are braces. +*** Formatting: +**** Control Statements: +- A space should be between the keyword and the condition. This is to make + control statements visually distinct from function calls. + +- Opening braces should be on the line after the control statement, and closing + braces on the line after the last statement, on it's own. This is to make the + scope of the control statement easily identifiable. + +- else and else if should always be on a new line, not the same line as an if + statement's closing brace. This is to more easily distinguish the seperate + blocks. + +- Control statements should never omit braces and do single statements. This is + mostly personal preference, but I do think it makes things more clear. + +*** Naming: +**** Rule 0: NEVER USE i AND j! +Never use the variable names i and j. These are easy to confuse, and often make +nested loops awful to read. Name these more descriptively. +For example: +- If you are using a variable to index an array, name the variable index. +- If you are indexing multiple arrays, name it "array name + Index". +- If you are using it to count something, call it count, or "name of the + thing you are counting + count". + +*** Comments: diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index df6ac0a..8f12c42 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -20,7 +20,7 @@ #include "../texteffects.h" #include "../inputoutput.h" -// A struct for bundling all needed paramaters for a thread so we can pass them using a void pointer: +// A struct for bundling all needed parameters for a thread so we can pass them using a void pointer: typedef struct threadparameters { gnutls_session_t tlsSession; @@ -36,17 +36,22 @@ typedef struct sockaddr sockaddr; // A globally available exit boolean: bool shouldExit = false; +// A function for managing the sending thread: void * messageSender(void * parameters) { - struct threadparameters *threadParameters = parameters; + threadparameters * threadParameters = parameters; + gnutls_session_t tlsSession = threadParameters->tlsSession; + FILE * loggingStream = threadParameters->loggingStream; + bool loggingFlag = threadParameters->loggingFlag; + WINDOW * window = threadParameters->window; userMessage sendBuffer; // Repeatedly get input from the user, place it in a userMessage, and send it to the server: while (!shouldExit) { // Print the prompt: - wprintw(threadParameters->window, "\n\n\nCOMM-LINK> "); - if (wgetnstr(threadParameters->window, sendBuffer.messageContent, MAX) == ERR) + wprintw(window, "\n\n\nCOMM-LINK> "); + if (wgetnstr(window, sendBuffer.messageContent, MAX) == ERR) { // Quit if there's any funny business with getting input: pthread_exit(NULL); @@ -59,77 +64,104 @@ void * messageSender(void * parameters) } // Send the message to the log if logging is enabled: - if (threadParameters->loggingFlag == true) + if (loggingFlag == true) { - fputs(sendBuffer.messageContent, threadParameters->loggingStream); - fputs("\n", threadParameters->loggingStream); - fflush(threadParameters->loggingStream); + fputs(sendBuffer.messageContent, loggingStream); + fputs("\n", loggingStream); + fflush(loggingStream); } // Send the message off to the server: - messageSend(threadParameters->tlsSession, &sendBuffer); + messageSend(tlsSession, &sendBuffer); } // Rejoin the main thread: pthread_exit(NULL); } +// A function for managing the receiving thread: void * messageReceiver(void * parameters) { + threadparameters * threadParameters = parameters; + gnutls_session_t tlsSession = threadParameters->tlsSession; + FILE * loggingStream = threadParameters->loggingStream; + int characterDelay = threadParameters->characterDelay; + bool loggingFlag = threadParameters->loggingFlag; + WINDOW * window = threadParameters->window; + int returnValue = 0; userMessage receiveBuffer; bool serverMessage = false; - struct threadparameters *threadParameters = parameters; int screenWidth = getmaxx(threadParameters->window); // Repeatedly take messages from the server and print them to the chat log window: while (!shouldExit) { - returnValue = messageReceive(threadParameters->tlsSession, &receiveBuffer); + // Get the next message: + returnValue = messageReceive(tlsSession, &receiveBuffer); + // Check we haven't been disconnected: - if(returnValue == -10 || returnValue == 0) + if (returnValue == -10 || returnValue == 0) { shouldExit = true; } + + // Check if it's a server message: else if (receiveBuffer.senderName[0] == '\0') { - wrapString(receiveBuffer.messageContent, - strlen(receiveBuffer.messageContent) - 1, screenWidth); + // Check if it's a command to disconnect: if (receiveBuffer.messageContent[0] == '\0') { shouldExit = true; pthread_exit(NULL); } - if(serverMessage == false) + + // Fit the string to the screen: + wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth); + + // If it's the first server message in a block, begin a block of server messages: + if (serverMessage == false) { - slowPrintNcurses("\n --====<>====--", threadParameters->characterDelay, threadParameters->window, true); + slowPrintNcurses("\n --====<>====--", characterDelay, window, true); serverMessage = true; } - slowPrintNcurses("\n", threadParameters->characterDelay, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, threadParameters->characterDelay, threadParameters->window, false); - slowPrintNcurses("\n", threadParameters->characterDelay, threadParameters->window, true); + + // Print the message: + slowPrintNcurses("\n", characterDelay, window, true); + slowPrintNcurses(receiveBuffer.messageContent, characterDelay, + window, false); + slowPrintNcurses("\n", characterDelay, window, true); } + // It's a user message: else { + // Fit the string to the screen: wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth - strlen(receiveBuffer.senderName) - 2); - if (threadParameters->loggingFlag == true) + + // If the user has requested logging, insert the message into the file: + if (loggingFlag == true) { - fputs(receiveBuffer.senderName, threadParameters->loggingStream); - fputs(": ", threadParameters->loggingStream); - fputs(receiveBuffer.messageContent, threadParameters->loggingStream); - fflush(threadParameters->loggingStream); + fputs(receiveBuffer.senderName, loggingStream); + fputs(": ", loggingStream); + fputs(receiveBuffer.messageContent, loggingStream); + fflush(loggingStream); } - if(serverMessage == true) + + // If we're in a block of server messages, end it: + if (serverMessage == true) { - slowPrintNcurses("\n --====<>====-- \n", threadParameters->characterDelay, threadParameters->window, true); + slowPrintNcurses("\n --====<>====-- \n", characterDelay, window, true); serverMessage = false; } - slowPrintNcurses(receiveBuffer.senderName, threadParameters->characterDelay, threadParameters->window, true); - slowPrintNcurses(": ", threadParameters->characterDelay, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, threadParameters->characterDelay, threadParameters->window, false); + + // Print the message: + slowPrintNcurses(receiveBuffer.senderName, characterDelay, window, true); + slowPrintNcurses(": ", characterDelay, window, true); + slowPrintNcurses(receiveBuffer.messageContent, characterDelay, window, false); } } + // Exit the thread if shouldExit is true: pthread_exit(NULL); } @@ -156,54 +188,54 @@ int main(int argc, char ** argv) { switch (currentopt) { - case 'i': - { - memcpy(ipAddress, optarg, 32); - break; - } - case 'c': - { - memcpy(chatLogPath, optarg, PATH_MAX + 1); - chatLog = fopen(chatLogPath, "a+"); - if (chatLog == NULL) + case 'i': { - chatLogging = false; + memcpy(ipAddress, optarg, 32); + break; } - else + case 'c': { - chatLogging = true; + memcpy(chatLogPath, optarg, PATH_MAX + 1); + chatLog = fopen(chatLogPath, "a+"); + if (chatLog == NULL) + { + chatLogging = false; + } + else + { + chatLogging = true; + } + break; } - break; - } - case 'g': - { - memcpy(gameLogPath, optarg, PATH_MAX + 1); - gameLog = fopen(gameLogPath, "a+"); - if (gameLog == NULL) + case 'g': { - gameLogging = false; + memcpy(gameLogPath, optarg, PATH_MAX + 1); + gameLog = fopen(gameLogPath, "a+"); + if (gameLog == NULL) + { + gameLogging = false; + } + else + { + gameLogging = true; + } + break; } - else + case 'p': { - gameLogging = true; + port = atoi(optarg); + break; + } + case 'd': + { + characterDelay = atoi(optarg); + break; + } + case '?': + { + return 1; + break; } - break; - } - case 'p': - { - port = atoi(optarg); - break; - } - case 'd': - { - characterDelay = atoi(optarg); - break; - } - case '?': - { - return 1; - break; - } } } @@ -218,9 +250,8 @@ int main(int argc, char ** argv) { slowPrint("Socket successfully created.\n", characterDelay); } - bzero(&serverAddress, sizeof(serverAddress)); - // Set our IP Address and port. Default to localhost for testing: + // Set our IP address and port. Default to localhost for testing: serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = inet_addr(ipAddress); serverAddress.sin_port = htons(port); @@ -231,17 +262,11 @@ int main(int argc, char ** argv) slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay); exit(0); } - else - { - slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", characterDelay); - } - usleep(100000); // Setup a GnuTLS session and initialize it: gnutls_session_t tlsSession = NULL; if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0) - { - // Failure Case + { exit(EXIT_FAILURE); } diff --git a/src/playerdata.h b/src/playerdata.h index e1d679f..8d0ae0c 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -9,10 +9,10 @@ #include "linkedlist.h" // Let the compiler know there will be structs defined elsewhere: -typedef struct playerPath playerPath; typedef struct playerArea playerArea; -typedef struct list list; +typedef struct playerPath playerPath; typedef struct listNode listNode; +typedef struct list list; typedef struct statBlock { diff --git a/tests/outputQueue-test.c b/tests/outputQueue-test.c deleted file mode 100644 index a36fd43..0000000 --- a/tests/outputQueue-test.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "../src/inputoutput.h" -#include -#include -int main() -{ - userMessage A, B, C; - strncpy(A.senderName, "Bob\0", 32 -1); - strncpy(A.messageContent, "joins the fray!\0", MAX-1); - strncpy(B.senderName, "Alice\0", 32 -1); - strncpy(B.messageContent, "challenges the unknown!\0", MAX -1); - strncpy(C.senderName, "Tom\0", 32 -1); - strncpy(C.messageContent, "Attacks them all!\0", MAX -1); - outputMessageQueue * testQueue = createOutputMessageQueue(); - printf("Queue Created.\n"); - printf("%d", queueOutputMessage(testQueue, A)); - printf("Queued A.\n"); - printf("%d", queueOutputMessage(testQueue, B)); - printf("Queued B.\n"); - printf("%d", queueOutputMessage(testQueue, C)); - printf("Queued C.\n"); - printf("%s\n", testQueue->front->content->senderName); - printf("%s\n", testQueue->front->content->messageContent); - printf("%s\n", testQueue->front->next->content->senderName); - printf("%s\n", testQueue->front->next->content->messageContent); - printf("%s\n", testQueue->front->next->next->content->senderName); - printf("%s\n", testQueue->front->next->next->content->messageContent); - printf("%s\n", testQueue->front->content->senderName); - dequeueOutputMessage(testQueue); - printf("%s\n", testQueue->front->content->senderName); - dequeueOutputMessage(testQueue); - printf("%s\n", testQueue->front->content->senderName); -// dequeueOutputMessage(testQueue); -// printf("%s\n", testQueue->front->content->senderName); - return 0; -} From a38cbb70a8b6dc22696f85bef8a8fafedc304025 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 8 Feb 2023 17:15:23 +0000 Subject: [PATCH 37/53] Fixed Thomas's Bug. In short: - When a large amount of input was recieved, the server would hang. - The server would hang on queue->lock for the input queue, in pushQueue(). - Upon debugging, it was revealed that queue->lock was actually false at this time. - GCC had optimized out the actual loop, leaving it to get stuck even though the variable had changed. - Adding the volatile keyword to the lock fixed this issue. --- Makefile | 2 +- src/queue.c | 3 +++ src/queue.h | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7abf4ec..e1478ab 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,6 @@ clean: rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug all: clean SilverMUDClient SilverMUDServer -all: CFLAGS += -Wall -Wextra -Ofast +all: CFLAGS += -Wall -Wextra -Ofast debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og -D debug debug: clean SilverMUDClientDebug SilverMUDServerDebug diff --git a/src/queue.c b/src/queue.c index d137525..74ba8e8 100644 --- a/src/queue.c +++ b/src/queue.c @@ -157,6 +157,9 @@ void popQueue(queue * queue) // Adds data to the back of a queue: void pushQueue(queue * queue, void * data, queueDataType type) { + // Check if the queue is locked: + while (queue->lock); + // Create a new node: queueNode * newNode = malloc(sizeof(queueNode)); newNode->type = type; diff --git a/src/queue.h b/src/queue.h index c2691df..797c173 100644 --- a/src/queue.h +++ b/src/queue.h @@ -37,7 +37,7 @@ typedef struct queueNode typedef struct queue { - bool lock; + volatile bool lock; size_t itemCount; queueNode * front; queueNode * back; From 52fd7ef6fb5102897b9243fd52cc3c037761641b Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Thu, 9 Feb 2023 21:53:23 +0000 Subject: [PATCH 38/53] Wrote more documentation and a rule for the style guide. Documented basic usage of the client's launch options to join a server and added the no one letter variable names rule. --- SilverMUD.org | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/SilverMUD.org b/SilverMUD.org index 0c748ff..6e0ad4b 100644 --- a/SilverMUD.org +++ b/SilverMUD.org @@ -6,6 +6,17 @@ to improvise that they have at the table, through simple programming and easy-to-understand structures. * Player's Guide +** Running The Client +*** How To Connect To A Server: +To connect to a server, use the command-line option =-i=, and the IP address of +the server. If the server admin is hosting the server on a port other than the +default port of 5000, you can use the =-p= option with the number of the port. If +the connection is successful, you will be placed in the server's login +area. Type =/join =, where player name is a name of your choosing, +and you will be placed in the spawn area, ready to play. + +*** Other Launch Options: + ** The Basic Commands SilverMUD is played through a set of very simple commands. To use a command, type a forward-slash (/) followed immediately by the command name. The command @@ -61,5 +72,14 @@ For example: - If you are indexing multiple arrays, name it "array name + Index". - If you are using it to count something, call it count, or "name of the thing you are counting + count". - + +**** Rule 1: No one letter variable names, unless in a mathematical function. +You should never use one letter variable names. They're needlessly obtuse and +you will not remember their meaning upon re-reading of the source code. The +exception to this is when you are writing a function which replicates a +mathematical formula or function with commonly accepted notation. However, you +should consider if it would be better to break mathematical convention for +clarity inside the program, such as when the variable names are the first letter +of a word or the mathematical notation uses many similar looking variables. + *** Comments: From feb17434252d76cd81ec41f6b1d41f6bdbad8a99 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 10 Feb 2023 23:33:36 +0000 Subject: [PATCH 39/53] Added naming rule 2 --- SilverMUD.org | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SilverMUD.org b/SilverMUD.org index 6e0ad4b..8fc4d46 100644 --- a/SilverMUD.org +++ b/SilverMUD.org @@ -82,4 +82,10 @@ should consider if it would be better to break mathematical convention for clarity inside the program, such as when the variable names are the first letter of a word or the mathematical notation uses many similar looking variables. +**** Rule 2: Prefer to use full words in variable and function names: +You should always prefer to use full words in variable and function names. It +makes the source code much easier to read, like a sentence. Ideally, if you want +to shorten the name, use synonyms or rephrasing before you resort to removing +letters. + *** Comments: From 66e0279e781fef6e6f7a3f6732c0e56084c50999 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sat, 11 Feb 2023 00:07:30 +0000 Subject: [PATCH 40/53] Commented the data structures in queue.h. - Commented all the data structures in queue, to make it slightly clearer as to their usage and what they store. --- src/queue.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/queue.h b/src/queue.h index 797c173..3217698 100644 --- a/src/queue.h +++ b/src/queue.h @@ -12,6 +12,7 @@ // Let the compiler know there will be structs defined elsewhere: typedef struct queue queue; +// An enum which is used to state what type of data is being stored in a queueNode: typedef enum queueDataType { EVENT, @@ -20,6 +21,7 @@ typedef enum queueDataType OUTPUT_MESSAGE } queueDataType; +// A union for storing a pointer to all the types of data a queueNode may hold: typedef union queueData { outputMessage * outputMessage; @@ -27,6 +29,7 @@ typedef union queueData commandEvent * command; } queueData; +// A queue node, a singly-linked list node for our queue: typedef struct queueNode queueNode; typedef struct queueNode { @@ -35,6 +38,7 @@ typedef struct queueNode queueNode * next; } queueNode; +// A queue, with pointers to the head and tail of the linked list, and data for multi-threading, locking, and an item count. typedef struct queue { volatile bool lock; From d0e4a8f9fc06f9813a2f585f324ab6ddeb80160b Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 12 Feb 2023 23:13:10 +0000 Subject: [PATCH 41/53] Removed the body of the try command. - Removed the try command's test functionality to allow for the later implementation of the actual functionality. --- src/gamelogic.c | 44 +++----------------------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index 60b3e85..a6b9f03 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -297,48 +297,10 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { userMessage * tryMessage = malloc(sizeof(userMessage)); tryMessage->senderName[0] = '\0'; - switch (getCoreStatFromString(currentCommand->arguments, 9)) - { - case STRENGTH: - { - switch (statCheck(currentCommand->caller, 20, STRENGTH)) - { - case CRITICAL_FAILURE: - { - strcpy(tryMessage->messageContent, "You weak, puny shit. Bet you don't even lift, bro.\n"); - break; - } - case FAILURE: - { - strcpy(tryMessage->messageContent, "Come on, bro, you should be able to get this set done.\n"); - break; - } - case SUCCESS: - { - strcpy(tryMessage->messageContent, "Nice set, bro. Keep it up.\n"); - break; - } - case CRITICAL_SUCCESS: - { - strcpy(tryMessage->messageContent, "HOLY SHIT, BRO! THAT'S SOME MAD REPS RIGHT THERE!\n"); - break; - } - default: - { - strcpy(tryMessage->messageContent, "I don't even, bro.\n"); - } - } - break; - } - default: - { - sprintf(tryMessage->messageContent,"%d", - skillCheck(currentCommand->caller, 10, currentCommand->arguments, strlen(currentCommand->arguments), - parameters->globalSkillList)); - break; - } - } + // Temporary message until we can implement objects, events, and challenges. + strcpy(tryMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n"); + // Allocate an outputMessage for the queue: outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, ¤tCommand->caller, 1); From 602f177a8f9b7d1eef5dc28bd36878b31aa06789 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 12 Feb 2023 23:32:39 +0000 Subject: [PATCH 42/53] Added some more comments. - Commented the data structures in areadata.h and gamelogic.h. --- src/areadata.h | 10 ++++++---- src/gamelogic.h | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/areadata.h b/src/areadata.h index e983019..327db34 100644 --- a/src/areadata.h +++ b/src/areadata.h @@ -9,30 +9,32 @@ // -=[ Area/Paths: ]=-: // ==================== +// Let the compiler know that we're going to define these types: typedef struct playerPath playerPath; typedef struct playerArea playerArea; +// A path, which contains a name, and a pointer to the area which the player will travel to: struct playerPath { char pathName[32]; playerArea * areaToJoin; }; +// An area, containing a list of paths exiting from the area, and a name and description of the area: struct playerArea { list * pathList; char areaName[32]; char areaDescription[MAX - 35]; -// playerPath * areaExits[16]; }; -// Create an area given a name and description: +// Create an area given a name and description, returning a pointer to the new area: playerArea * createArea(char * nameString, char * descriptionString); -// Create a path between two areas given two areas and two strings: +// Create a path between two areas given two areas and two strings, adding it to the both area's list of paths: int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); -// Create a one-way path between two areas given two areas and a string: +// Create a one-way path between two areas given two areas and a string, adding it to the first area's list of paths: int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description); // TO BE IMPLEMENTED: diff --git a/src/gamelogic.h b/src/gamelogic.h index 631ef6b..cc672e4 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -12,6 +12,7 @@ // -=[ Data Structures ]=-: // ======================== +// An event for storing the information typedef struct commandEvent commandEvent; typedef struct commandEvent { @@ -83,6 +84,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); // -=[ Gameplay Primitives ]=-: // ============================ +// The possible outcomes of a check or challenge: typedef enum outcome { CRITICAL_FAILURE, From f411333203df3d0ab01e9f4701fd81a477bcac0a Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Mon, 13 Feb 2023 17:23:30 +0000 Subject: [PATCH 43/53] Refactored SilverMUD to use the queue type for command events. - Removed the old commandQueue type. - Removed the functions relating to creating, pushing, popping, and peeking the commandQueues. - Refactored the gameLogicHandler to use a queue type. --- src/gamelogic.c | 161 ++++-------------------------------------------- src/gamelogic.h | 28 +-------- 2 files changed, 16 insertions(+), 173 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index a6b9f03..adcc1ae 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -21,11 +21,11 @@ void * gameLogicHandler(void * parameters) { gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; - commandQueue * commandQueue = createCommandQueue(); + queue * commandQueue = createQueue(); while(true) { // Evaluate remaining commands: - while(commandQueue->currentLength != 0) + while(commandQueue->itemCount != 0) { evaluateNextCommand(threadParameters, commandQueue); } @@ -93,30 +93,8 @@ void * gameLogicHandler(void * parameters) pthread_exit(NULL); } -// Create a commandQueue: -commandQueue * createCommandQueue(void) -{ - commandQueue * newCommandQueue = calloc(1, sizeof(commandQueue)); - newCommandQueue->back = NULL; - newCommandQueue->front = NULL; - newCommandQueue->lock = false; - newCommandQueue->paused = false; - newCommandQueue->currentLength = 0; - return newCommandQueue; -} - -// Return the front commandEvent from a commandQueue: -commandEvent * peekCommand(commandQueue * queue) -{ - // Do nothing until the command queue is unlocked. - while(queue->lock); - - // Return the front item. - return queue->front; -} - // Enqueue a messaged command to a commandQueue: -int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) +void queueMessagedCommand(queue * queue, inputMessage * messageToQueue) { // Prepare the new commandEvent: commandEvent * newCommand = calloc(1, sizeof(commandEvent)); @@ -145,48 +123,11 @@ int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) *character = tolower(*character); } - // Wait for the queue to unlock: - while (queue->lock); - - // Check that we're not overflowing the queue: - if ((queue->currentLength + 1) > MAXQUEUELENGTH) - { - // Free the new command, it's getting dumped: - free(newCommand); - // Unlock the queue: - queue->lock = false; - return -1; - } - else - { - // If the queue is empty, set the first commandEvent as both the front and back of the queue: - if(queue->front == NULL) - { - queue->front = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - else - { - queue->back->next = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - } + pushQueue(queue, newCommand, COMMAND); } // Enqueue a command to a commandQueue: -int queueCommand(commandQueue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer) +void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer) { // Prepare the new commandEvent: commandEvent * newCommand = calloc(1, sizeof(commandEvent)); @@ -196,96 +137,20 @@ int queueCommand(commandQueue * queue, char * command, char * arguments, int com // Copy the command and arguments: strncpy(newCommand->command, command, commandLength); - strncpy(newCommand->arguments, arguments, argumentsLength); - + if(argumentsLength > 0) + { + strncpy(newCommand->arguments, arguments, argumentsLength); + } // Ensure the arguments are safe to parse, without adding newlines: userNameSanatize(newCommand->command, 16); - // Wait for the queue to unlock: - while (queue->lock); - - // 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 commandEvent as both the front and back of the queue: - if(queue->front == NULL) - { - queue->front = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - else - { - queue->back->next = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - } -} - -// Dequeue the front commandEvent from a commandQueue: -int dequeueCommand(commandQueue * queue) -{ - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check the list isn't empty: - if(queue->front == NULL) - { - queue->lock = false; - return -1; - } - - // If there is only one item in the queue: - else if(queue->front == queue->back) - { - free(queue->front->command); - free(queue->front->arguments); - free(queue->front); - queue->front = NULL; - queue->back = NULL; - queue->currentLength--; - queue->lock = false; - return 0; - } - - // Remove the front item: - else - { - commandEvent * commandToDelete = queue->front; - queue->front = queue->front->next; - free(commandToDelete->command); - free(commandToDelete->arguments); - free(commandToDelete); - queue->currentLength--; - queue->lock = false; - return 0; - } + pushQueue(queue, newCommand, COMMAND); } // Evaluate the next commandEvent: -int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) +int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) { - commandEvent * currentCommand = peekCommand(queue); + commandEvent * currentCommand = peekQueue(queue)->data.command; while(queue->lock); queue->lock = true; if(currentCommand == NULL) @@ -686,7 +551,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) // Remove the current command and unlock the queue: currentCommand = NULL; queue->lock = false; - dequeueCommand(queue); + popQueue(queue); return 0; } diff --git a/src/gamelogic.h b/src/gamelogic.h index cc672e4..83c96f6 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -22,16 +22,6 @@ typedef struct commandEvent char * arguments; } commandEvent; -// A first-in first-out queue for message input from players: -typedef struct commandQueue -{ - bool lock; - bool paused; - int currentLength; - commandEvent * back; - commandEvent * front; -} commandQueue; - // A data-structure containing the needed parameters for a main game loop: typedef struct gameLogicParameters { @@ -58,27 +48,15 @@ int movePlayerToArea(playerInfo * player, char * requestedPath); // Thread function which runs the main game loop, given the needed parameters: void * gameLogicHandler(void * parameters); -// Create a commandQueue: -commandQueue * createCommandQueue(void); - // Enqueue a command to a commandQueue: -int queueCommand(commandQueue * queue, char * command, char * arguments, +void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength , playerInfo * callingPlayer); // Enqueue a messaged command to a commandQueue: -int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue); - -// Dequeue the front commandEvent from a commandQueue: -int dequeueCommand(commandQueue * queue); - -// Return the front commandEvent from a commandQueue: -commandEvent * peekCommand(commandQueue * queue); +void queueMessagedCommand(queue * queue, inputMessage * messageToQueue); // Evaluate the next commandEvent: -int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); - -/* // Evaluate the given commandEvent: */ -/* int evaluateCommand(gameLogicParameters * parameters, commandEvent * command); */ +int evaluateNextCommand(gameLogicParameters * parameters, queue * queue); // ============================ // -=[ Gameplay Primitives ]=-: From c7531828274f5a725a31238d7d50ce6a83e8f96f Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Feb 2023 21:22:14 +0000 Subject: [PATCH 44/53] Cleaned up inputoutput.c. - Added additional comments in sections functions that were unclear. - Renamed targetIndex to sentCount, in order to clarify the usage of the variable. - Linted according to the current SilverMUD style guide. --- src/inputoutput.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/inputoutput.c b/src/inputoutput.c index 4bd4b10..b5cb6c3 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -17,11 +17,14 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) { int returnValue = 0; + // Continuously attempt to send the name field until it succeeds or fatally errors: do { returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + + // Continuously attempt to send the message field until it succeeds or fatally errors: do { returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, @@ -35,11 +38,14 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) { int returnValue = 0; + // Continuously attempt to receive the name field until it succeeds or fatally errors: do { returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + + // Continuously attempt to receive the message field until it succeeds or fatally errors: do { returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, @@ -76,17 +82,19 @@ void * outputThreadHandler(void * parameters) gnutls_session_t * tlssessions = variables->tlssessions; playerInfo * connectedPlayers = variables->connectedPlayers; - while(true) + while (true) { - if(outputQueue->itemCount == 0) + // If there's nothing to do, put the thread to sleep: + if (outputQueue->itemCount == 0) { pthread_cond_wait(&outputQueue->condition, &outputQueue->mutex); } - // Run through the output queue and send all unused messages: - while(outputQueue->itemCount != 0) + + // Run through the output queue and send all unsent messages: + while (outputQueue->itemCount != 0) { // Wait until the queue unlocks: - while(outputQueue->lock); + while (outputQueue->lock); // Lock the queue: outputQueue->lock = true; @@ -98,30 +106,32 @@ void * outputThreadHandler(void * parameters) outputQueue->lock = false; // If the first target is set to NULL, it's intended for all connected: - if(message->recipientsCount == 0) + if (message->recipientsCount == 0) { for (int index = 0; index < PLAYERCOUNT; index++) { messageSend(tlssessions[index], message->content); } } + // Otherwise, send it only to the targeted players: else { - int targetIndex = 0; - for(int index = 0; index < PLAYERCOUNT; index++) + int sentCount = 0; + for (int index = 0; index < PLAYERCOUNT; index++) { - if(targetIndex == message->recipientsCount) + if (sentCount == message->recipientsCount) { break; } - if(&connectedPlayers[index] == message->recipients[targetIndex]) + if (&connectedPlayers[index] == message->recipients[targetIndex]) { - targetIndex++; + sentCount++; messageSend(tlssessions[index], message->content); } } } + // Remove the output message from the queue: popQueue(outputQueue); } @@ -131,9 +141,9 @@ void * outputThreadHandler(void * parameters) // Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length) { - for(int index = 0; index <= length; index++) + for (int index = 0; index <= length; index++) { - // If it's not a printable character, it has no buisness being here: + // If it's not a printable character, it has no business being here: if(!isprint(inputString[index])) { inputString[index] = '\n'; @@ -141,6 +151,7 @@ void userInputSanatize(char * inputString, int length) break; } } + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } @@ -150,7 +161,7 @@ void userNameSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) { - // If it's not a printable character, it has no buisness being here: + // If it's not a printable character, it has no business being here: if(!isprint(inputString[index])) { inputString[index] = '\0'; From c2af4a551a4e44e8e53579cac1927341be40e46c Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Feb 2023 21:30:40 +0000 Subject: [PATCH 45/53] Cleaned up inputoutput.h - Changed the comments about the data structures to be more descriptive about their function. --- src/inputoutput.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inputoutput.h b/src/inputoutput.h index f554832..c7d6fc0 100644 --- a/src/inputoutput.h +++ b/src/inputoutput.h @@ -26,14 +26,14 @@ typedef struct userMessage char messageContent[MAX]; } userMessage; -// Contains a userMessage and a pointer to the playerInfo of the connection which sent it: +// Contains a message sent to the server and a pointer to the playerInfo of the connection which sent it: typedef struct inputMessage { playerInfo * sender; userMessage * content; } inputMessage; -// Contains a userMessage and pointers to the playerInfo of the recipients and the number of them: +// Contains a message to be sent, the amount of recipients, and pointers to their playerInfo: typedef struct outputMessage { int recipientsCount; @@ -41,7 +41,7 @@ typedef struct outputMessage playerInfo ** recipients; } outputMessage; -// Contains the relevant parameters for the outputThreadLoop function: +// Contains pointers to the necessary information to be shared outputThreadHandler function: typedef struct outputThreadParameters { queue * outputQueue; From f31f0c79a55681f7d4d1d4886bbf2bde7dc25483 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Feb 2023 21:43:13 +0000 Subject: [PATCH 46/53] Cleaned up gamelogic.c/h - Improved comments to clarify the purpose and usage of the data structures and functions. - Brought the files in line with the SilverMUD style guide. --- src/gamelogic.c | 125 ++++++++++++++++++++++++------------------------ src/gamelogic.h | 25 +++++----- 2 files changed, 74 insertions(+), 76 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index adcc1ae..fdb0d2b 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -22,29 +22,29 @@ void * gameLogicHandler(void * parameters) gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; queue * commandQueue = createQueue(); - while(true) + while (true) { // Evaluate remaining commands: - while(commandQueue->itemCount != 0) + while (commandQueue->itemCount != 0) { evaluateNextCommand(threadParameters, commandQueue); } // Wait if there is nothing to do: - if(threadParameters->inputQueue->itemCount == 0) + if (threadParameters->inputQueue->itemCount == 0) { pthread_cond_wait(&threadParameters->inputQueue->condition, &threadParameters->inputQueue->mutex); } // Check for new messages and pop them off the queue: - if(threadParameters->inputQueue->itemCount != 0) + if (threadParameters->inputQueue->itemCount != 0) { - while(threadParameters->inputQueue->lock == true); + while (threadParameters->inputQueue->lock == true); threadParameters->inputQueue->lock = true; currentInput = peekQueue(threadParameters->inputQueue)->data.inputMessage; userInputSanatize(currentInput->content->messageContent, MAX); // A slash as the first character means the message is a user command: - if(currentInput->content->messageContent[0] == '/') + if (currentInput->content->messageContent[0] == '/') { queueMessagedCommand(commandQueue, currentInput); } @@ -93,7 +93,7 @@ void * gameLogicHandler(void * parameters) pthread_exit(NULL); } -// Enqueue a messaged command to a commandQueue: +// Evaluate the next commandEvent in a queue: void queueMessagedCommand(queue * queue, inputMessage * messageToQueue) { // Prepare the new commandEvent: @@ -126,8 +126,9 @@ void queueMessagedCommand(queue * queue, inputMessage * messageToQueue) pushQueue(queue, newCommand, COMMAND); } -// Enqueue a command to a commandQueue: -void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer) +// Enqueue a command to a queue: +void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, + playerInfo * callingPlayer) { // Prepare the new commandEvent: commandEvent * newCommand = calloc(1, sizeof(commandEvent)); @@ -137,7 +138,7 @@ void queueCommand(queue * queue, char * command, char * arguments, int commandLe // Copy the command and arguments: strncpy(newCommand->command, command, commandLength); - if(argumentsLength > 0) + if (argumentsLength > 0) { strncpy(newCommand->arguments, arguments, argumentsLength); } @@ -147,18 +148,18 @@ void queueCommand(queue * queue, char * command, char * arguments, int commandLe pushQueue(queue, newCommand, COMMAND); } -// Evaluate the next commandEvent: +// Evaluate the next commandEvent in a queue: int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) { commandEvent * currentCommand = peekQueue(queue)->data.command; - while(queue->lock); + while (queue->lock); queue->lock = true; - if(currentCommand == NULL) + if (currentCommand == NULL) { return -1; } // Try command: Attempt to use a stat or skill on an object: - if(strncmp(currentCommand->command, "try", 3) == 0) + if (strncmp(currentCommand->command, "try", 3) == 0) { userMessage * tryMessage = malloc(sizeof(userMessage)); tryMessage->senderName[0] = '\0'; @@ -176,7 +177,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) free(tryMessage); } // Exit command: Sends an "empty" exit message to disconnect a client: - if(strncmp(currentCommand->command, "exit", 4) == 0) + if (strncmp(currentCommand->command, "exit", 4) == 0) { // Allocate a userMessage containing null characters as the first char in both fields: userMessage * exitMessage = malloc(sizeof(userMessage)); @@ -194,15 +195,15 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) } // Move command: Moves the caller to a different area given a path name or number: - if(strncmp(currentCommand->command, "move", 4) == 0) + if (strncmp(currentCommand->command, "move", 4) == 0) { char requestedPath[32]; - if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area) + if (strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area) { memcpy(requestedPath, currentCommand->arguments, 32); userNameSanatize(requestedPath, 32); requestedPath[31] = '\0'; - if(movePlayerToArea(currentCommand->caller, requestedPath) == 0) + if (movePlayerToArea(currentCommand->caller, requestedPath) == 0) { // Call the look command after moving. It's fine to unlock, because the loop won't // continue until the command is queued: @@ -214,7 +215,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) } // Look command: Returns the description of the current area and paths: - if(strncmp(currentCommand->command, "look", 4) == 0) + if (strncmp(currentCommand->command, "look", 4) == 0) { char formattedString[64]; userMessage * lookMessage = calloc(1, sizeof(userMessage)); @@ -236,11 +237,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) int charCount = 13; strncat(lookMessage->messageContent, "You can go:", 13); - if(currentCommand->caller->currentArea->pathList->itemCount > 0) + if (currentCommand->caller->currentArea->pathList->itemCount > 0) { for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++) { - if((charCount + 64) >= MAX) + if ((charCount + 64) >= MAX) { lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); @@ -265,23 +266,23 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) } // Join command: Allows the player to join the game given a name: // TODO: Implement login/character creation. Will be a while: - if(strncmp(currentCommand->command, "join", 4) == 0) + if (strncmp(currentCommand->command, "join", 4) == 0) { - if(currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area) + if (currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area) { bool validName = true; for(int index = 0; index < *parameters->playerCount; index++) { - if(currentCommand->arguments[0] == '\0') + if (currentCommand->arguments[0] == '\0') { validName = false; } - if(strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) + if (strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) { validName = false; } } - if(validName) + if (validName) { strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16); currentCommand->caller->currentArea = getFromList(parameters->areaList, 1)->area; @@ -294,12 +295,12 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) } } // Talk command: Allows the player to begin a chat session with another player: - if(strncmp(currentCommand->command, "talk", 4) == 0) + if (strncmp(currentCommand->command, "talk", 4) == 0) { // TODO: Implement. } // Stat command: Displays the current character's sheet. - if(strncmp(currentCommand->command, "stat", 4) == 0) + if (strncmp(currentCommand->command, "stat", 4) == 0) { char * formattedString = calloc(121, sizeof(char)); userMessage * statMessage = calloc(1, sizeof(userMessage)); @@ -319,7 +320,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) strncat(statMessage->messageContent, formattedString, 120); // Levelling stats: Current XP, and spec points. - if(currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) + if (currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) { snprintf(formattedString, 120, "Current Experience: %ld | Spec Points Available: %d | Skill Points Available: %d", currentCommand->caller->stats->experience, currentCommand->caller->stats->specPoints, currentCommand->caller->stats->skillPoints); @@ -337,20 +338,20 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); bzero(statMessage->messageContent, sizeof(char) * MAX); - if(currentCommand->caller->skills->head != NULL) + if (currentCommand->caller->skills->head != NULL) { size_t skillIndex = 0; int charCount = 0; bool addNewline = false; playerSkill * skill; - while(skillIndex < currentCommand->caller->skills->itemCount) + while (skillIndex < currentCommand->caller->skills->itemCount) { skill = getFromList(currentCommand->caller->skills, skillIndex)->skill; skillIndex++; snprintf(formattedString, 120, "| %2d | %31s ", skill->skillPoints, skill->skillName); charCount += 43; strncat(statMessage->messageContent, formattedString, 120); - if((charCount + 43) >= MAX) + if ((charCount + 43) >= MAX) { // Allocate an outputMessage for the queue: statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); @@ -361,7 +362,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) charCount = 0; break; } - else if(addNewline) + else if (addNewline) { strncat(statMessage->messageContent, "|\n", 3); charCount++; @@ -383,18 +384,18 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) } // Spec command: Assign spec points to stats: - if(strncmp(currentCommand->command, "spec", 4) == 0) + if (strncmp(currentCommand->command, "spec", 4) == 0) { userMessage * specMessage = calloc(1, sizeof(userMessage)); specMessage->senderName[0] = '\0'; char * formattedString = calloc(121, sizeof(char)); - if(currentCommand->caller->stats->specPoints > 0) + if (currentCommand->caller->stats->specPoints > 0) { int selectedAmount = 0; strtok(currentCommand->arguments, " "); selectedAmount = atoi(¤tCommand->arguments[strlen(currentCommand->arguments) + 1]); coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16); - if(selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) + if (selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) { switch (selectedStat) { @@ -464,11 +465,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) free(specMessage); free(formattedString); } - if(strncmp(currentCommand->command, "skill", 5) == 0) + if (strncmp(currentCommand->command, "skill", 5) == 0) { userMessage * skillMessage = calloc(1, sizeof(userMessage)); skillMessage->senderName[0] = '\0'; - if((currentCommand->caller->stats->skillPoints - 1) >= 0) + if ((currentCommand->caller->stats->skillPoints - 1) >= 0) { int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments, strlen(currentCommand->arguments), currentCommand->caller); @@ -502,7 +503,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) free(skillMessage); } - if(strncmp(currentCommand->command, "listskills", 10) == 0) + if (strncmp(currentCommand->command, "listskills", 10) == 0) { userMessage * listMessage = calloc(1, sizeof(userMessage)); char * formattedString = calloc(121, sizeof(char)); @@ -510,13 +511,13 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) size_t skillIndex = 0; bool addNewline = false; playerSkill * currentSkill; - while(skillIndex < parameters->globalSkillList->itemCount) + while (skillIndex < parameters->globalSkillList->itemCount) { currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill; snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName); charCount += 43; strncat(listMessage->messageContent, formattedString, 120); - if((charCount + 46) >= MAX) + if ((charCount + 46) >= MAX) { // Allocate an outputMessage for the queue: outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); @@ -528,7 +529,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) charCount = 0; addNewline = false; } - else if(addNewline) + else if (addNewline) { strncat(listMessage->messageContent, "|\n", 3); charCount++; @@ -555,11 +556,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) return 0; } -// Run a stat check: +// Run a stat check for the given player, returning an outcome: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) { // Calculate the chance: - if(chance > 100 || chance < 0) + if (chance > 100 || chance < 0) { return ERROR; } @@ -600,9 +601,9 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } } int attempt = (random() % 100) + modifier; - if(attempt >= chance) + if (attempt >= chance) { - if(attempt >= 98) + if (attempt >= 98) { return CRITICAL_SUCCESS; } @@ -613,7 +614,7 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } else { - if(attempt <= 2) + if (attempt <= 2) { return CRITICAL_FAILURE; } @@ -624,11 +625,11 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } } -// Run a skill check: +// Run a skill check for the given player, returning an outcome: outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList) { // Calculate the chance: - if(chance > 100 || chance < 0) + if (chance > 100 || chance < 0) { return ERROR; } @@ -637,9 +638,9 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski // Check if the player has the given skill: bool playerHasSkill = false; size_t playerIndex = 0; - while(playerIndex < player->skills->itemCount) + while (playerIndex < player->skills->itemCount) { - if(strncmp(skillName, getFromList(player->skills, playerIndex)->skill->skillName, skillNameLength) != 0) + if (strncmp(skillName, getFromList(player->skills, playerIndex)->skill->skillName, skillNameLength) != 0) { playerHasSkill = true; break; @@ -650,9 +651,9 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski // If the player doesn't have the skill, check if it's in the game and is trained: bool trainedSkill = false; size_t globalIndex = 0; - while(globalIndex < globalSkillList->itemCount) + while (globalIndex < globalSkillList->itemCount) { - if(strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) != 0) + if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) != 0) { trainedSkill = getFromList(globalSkillList, globalIndex)->skill->trainedSkill; break; @@ -662,20 +663,20 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski // Calculate the modifier: int modifier = 0; - if(trainedSkill) + if (trainedSkill) { modifier = -100; } - else if(playerHasSkill) + else if (playerHasSkill) { modifier = getFromList(player->skills, playerIndex)->skill->skillModifier * 4; } // Attempt the check: int attempt = (random() % 100) + modifier; - if(attempt >= chance) + if (attempt >= chance) { - if(attempt >= 98) + if (attempt >= 98) { return CRITICAL_SUCCESS; } @@ -686,7 +687,7 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski } else { - if(attempt <= 2) + if (attempt <= 2) { return CRITICAL_FAILURE; } @@ -697,14 +698,14 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski } } -// Move a player to a different area given a path in the area: +// Move a player along a path in their current area: int movePlayerToArea(playerInfo * player, char * requestedPath) { // Check if a number was given first: size_t selected = atoi(requestedPath); - if(selected != 0 && !(selected > player->currentArea->pathList->itemCount)) + if (selected != 0 && !(selected > player->currentArea->pathList->itemCount)) { - if(getFromList(player->currentArea->pathList, selected - 1)->path != NULL && + if (getFromList(player->currentArea->pathList, selected - 1)->path != NULL && getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL) { player->currentArea = getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin; @@ -719,7 +720,7 @@ int movePlayerToArea(playerInfo * player, char * requestedPath) // Otherwise search for the description: for (size_t index = 0; index < player->currentArea->pathList->itemCount; index++) { - if(strncmp(getFromList(player->currentArea->pathList, index)->path->pathName, + if (strncmp(getFromList(player->currentArea->pathList, index)->path->pathName, requestedPath, 32) == 0) { printf("%s: %s\n", player->playerName, getFromList(player->currentArea->pathList, index)->path->pathName); diff --git a/src/gamelogic.h b/src/gamelogic.h index 83c96f6..1e717c5 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -12,7 +12,7 @@ // -=[ Data Structures ]=-: // ======================== -// An event for storing the information +// An event for storing the information needed to evaluate a command: typedef struct commandEvent commandEvent; typedef struct commandEvent { @@ -22,7 +22,7 @@ typedef struct commandEvent char * arguments; } commandEvent; -// A data-structure containing the needed parameters for a main game loop: +// A data-structure containing the needed parameters for the main game loop: typedef struct gameLogicParameters { // Players: @@ -42,22 +42,19 @@ typedef struct gameLogicParameters // -=[ Functions ]=-: // ======================== -// Player movement: -int movePlayerToArea(playerInfo * player, char * requestedPath); - // Thread function which runs the main game loop, given the needed parameters: void * gameLogicHandler(void * parameters); -// Enqueue a command to a commandQueue: -void queueCommand(queue * queue, char * command, char * arguments, - int commandLength, int argumentsLength , playerInfo * callingPlayer); - -// Enqueue a messaged command to a commandQueue: +// Enqueue a command that has been sent as a message from a user to a queue: void queueMessagedCommand(queue * queue, inputMessage * messageToQueue); -// Evaluate the next commandEvent: +// Evaluate the next commandEvent in a queue: int evaluateNextCommand(gameLogicParameters * parameters, queue * queue); +// Enqueue a command to a queue: +void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, + playerInfo * callingPlayer); + // ============================ // -=[ Gameplay Primitives ]=-: // ============================ @@ -72,13 +69,13 @@ typedef enum outcome ERROR } outcome; -// Player movement: +// Move a player along a path in their current area: int movePlayerToArea(playerInfo * player, char * requestedPath); -// Run a stat check: +// Run a stat check for the given player, returning an outcome: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); -// Run a skill check: +// Run a skill check for the given player, returning an outcome: outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList); #endif From f5cb3ad16ed4f3d75f9ee39ec5fe7c9981c4646a Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Feb 2023 22:09:21 +0000 Subject: [PATCH 47/53] More cleaning up. - Brought remaining files in line with style guides, and improved comments. --- src/inputoutput.c | 4 +-- src/linkedlist.c | 68 +++++++++++++++++++++++------------------------ src/linkedlist.h | 6 ++++- src/playerdata.c | 51 ++++++++++++++++++----------------- src/playerdata.h | 6 ++++- src/texteffects.c | 37 +++++++++++++++----------- src/texteffects.h | 23 ++++++++-------- 7 files changed, 105 insertions(+), 90 deletions(-) diff --git a/src/inputoutput.c b/src/inputoutput.c index b5cb6c3..b8e4136 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -124,7 +124,7 @@ void * outputThreadHandler(void * parameters) { break; } - if (&connectedPlayers[index] == message->recipients[targetIndex]) + if (&connectedPlayers[index] == message->recipients[sentCount]) { sentCount++; messageSend(tlssessions[index], message->content); @@ -156,7 +156,7 @@ void userInputSanatize(char * inputString, int length) inputString[length - 1] = '\0'; } -// Sanatize user names so they display correctly; +// Sanatize user names so they display correctly: void userNameSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) diff --git a/src/linkedlist.c b/src/linkedlist.c index 71154e3..59e7bab 100644 --- a/src/linkedlist.c +++ b/src/linkedlist.c @@ -11,7 +11,7 @@ static inline void deallocateListNode(listNode * node, listDataType type) { // Delete the node: - switch(type) + switch (type) { case PLAYER: { @@ -51,7 +51,7 @@ list * createList(listDataType type) newList->itemCount = 0; newList->head = NULL; newList->tail = NULL; - + // Return the new list: return newList; } @@ -60,7 +60,7 @@ list * createList(listDataType type) int destroyList(list ** list) { // Check if the list is empty: - if((*list)->itemCount == 0) + if ((*list)->itemCount == 0) { free(*list); list = NULL; @@ -68,7 +68,7 @@ int destroyList(list ** list) } else { - while((*list)->itemCount > 0) + while ((*list)->itemCount > 0) { removeFromList((*list), (*list)->type, (*list)->itemCount - 1); } @@ -82,13 +82,13 @@ int destroyList(list ** list) listData * getFromList(list * list, size_t listIndex) { // Check that we were given a valid index: - if(listIndex > (list->itemCount - 1)) + if (listIndex > (list->itemCount - 1)) { perror("Invalid index specified.\n"); return NULL; } // Return the head if index is 0: - else if(listIndex == 0) + else if (listIndex == 0) { return &(list->head->data); } @@ -96,7 +96,7 @@ listData * getFromList(list * list, size_t listIndex) else { listNode * currentNode = list->head; - while(listIndex-- > 0) + while (listIndex-- > 0) { currentNode = currentNode->next; } @@ -108,23 +108,23 @@ listData * getFromList(list * list, size_t listIndex) listNode * getNodeFromList(list * list, size_t listIndex) { // Check that we were given a valid index: - if(listIndex > (list->itemCount - 1)) + if (listIndex > (list->itemCount - 1)) { perror("Invalid index specified.\n"); return NULL; } // Return the head if index is 0: - else if(listIndex == 0) + else if (listIndex == 0) { return list->head; } // Loop through the entries in the list until we get to the right one: else { - if((list->itemCount / 2) < listIndex) + if ((list->itemCount / 2) < listIndex) { listNode * currentNode = list->tail; - while(listIndex-- > 0) + while (listIndex-- > 0) { currentNode = currentNode->previous; } @@ -133,7 +133,7 @@ listNode * getNodeFromList(list * list, size_t listIndex) else { listNode * currentNode = list->head; - while(listIndex-- > 0) + while (listIndex-- > 0) { currentNode = currentNode->next; } @@ -146,14 +146,14 @@ listNode * getNodeFromList(list * list, size_t listIndex) listNode * addToList(list * list, void * data, listDataType type) { // Check the type: - if(type != list->type) + if (type != list->type) { fprintf(stderr, "Not the correct type for this list.\n"); return NULL; } // If this is the first item in the list: - if(list->itemCount == 0) + if (list->itemCount == 0) { // Allocate the new node for the list: list->head = calloc(1, sizeof(listNode)); @@ -164,7 +164,7 @@ listNode * addToList(list * list, void * data, listDataType type) list->tail = list->head; // Add the data to the new node: - switch(type) + switch (type) { case PATH: { @@ -194,7 +194,7 @@ listNode * addToList(list * list, void * data, listDataType type) list->tail->next = calloc(1, sizeof(listNode)); // Add the data to the new node: - switch(type) + switch (type) { case PATH: { @@ -235,26 +235,26 @@ listNode * addToList(list * list, void * data, listDataType type) listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex) { // Check that the types are correct: - if(list->type != type) + if (list->type != type) { fprintf(stderr, "Types do not match.\n"); return NULL; } // Handle the special case of adding to the end of the list: - if(listIndex == (list->itemCount - 1)) + if (listIndex == (list->itemCount - 1)) { return addToList(list, data, type); } // Handle the special case of adding to the beginning of the list: - if(listIndex == 0) + if (listIndex == 0) { // Create the new node: listNode * newNode = calloc(1, sizeof(listNode)); // Add the data to the node: - switch(type) + switch (type) { case PATH: { @@ -290,7 +290,7 @@ listNode * insertIntoList(list * list, void * data, listDataType type, size_t li } // Check that the index is valid: - if(listIndex > (list->itemCount - 1)) + if (listIndex > (list->itemCount - 1)) { fprintf(stderr, "Index is invalid for the list.\n"); return NULL; @@ -313,7 +313,7 @@ listNode * insertIntoList(list * list, void * data, listDataType type, size_t li previousNode->next->previous = previousNode; // Add the data to the node: - switch(type) + switch (type) { case PATH: { @@ -344,7 +344,7 @@ listNode * insertIntoList(list * list, void * data, listDataType type, size_t li bool deleteFromList(list * list, void * data, listDataType type) { size_t index = 0; - if(getIndexFromList(list, data, type, &index) == false) + if (getIndexFromList(list, data, type, &index) == false) { return false; } @@ -359,19 +359,19 @@ bool deleteFromList(list * list, void * data, listDataType type) int removeFromList(list * list, listDataType type, size_t listIndex) { // Check that we're removing the correct type: - if(list->type != type) + if (list->type != type) { return -1; } // Check the list index is valid: - if(listIndex > list->itemCount - 1) + if (listIndex > list->itemCount - 1) { return -2; } // The first node in the list: - if(listIndex == 0) + if (listIndex == 0) { // Get the current head and move the list's head on: listNode * oldHead = list->head; @@ -379,7 +379,7 @@ int removeFromList(list * list, listDataType type, size_t listIndex) // If we haven't removed the last item, set the previous pointer // in the new head to null. - if(list->head != NULL) + if (list->head != NULL) { list->head->previous = NULL; } @@ -392,7 +392,7 @@ int removeFromList(list * list, listDataType type, size_t listIndex) return list->itemCount; } // The last node in the list: - else if(listIndex == (list->itemCount - 1)) + else if (listIndex == (list->itemCount - 1)) { // Move the tail up by one: list->tail = list->tail->previous; @@ -430,7 +430,7 @@ int removeFromList(list * list, listDataType type, size_t listIndex) bool getIndexFromList(list * list, void * data, listDataType type, size_t * index) { // Check the list types are the same: - if(list->type == type) + if (list->type == type) { fprintf(stderr, "List types do not match.\n"); return false; @@ -438,11 +438,11 @@ bool getIndexFromList(list * list, void * data, listDataType type, size_t * inde for(*index = 0; *index < list->itemCount; *index += 1) { - switch(type) + switch (type) { case AREA: { - if(getFromList(list, *index)->area == data) + if (getFromList(list, *index)->area == data) { return true; } @@ -450,7 +450,7 @@ bool getIndexFromList(list * list, void * data, listDataType type, size_t * inde } case PLAYER: { - if(getFromList(list, *index)->player == data) + if (getFromList(list, *index)->player == data) { return true; } @@ -458,7 +458,7 @@ bool getIndexFromList(list * list, void * data, listDataType type, size_t * inde } case PATH: { - if(getFromList(list, *index)->path == data) + if (getFromList(list, *index)->path == data) { return true; } @@ -466,7 +466,7 @@ bool getIndexFromList(list * list, void * data, listDataType type, size_t * inde } case SKILL: { - if(getFromList(list, *index)->skill == data) + if (getFromList(list, *index)->skill == data) { return true; } diff --git a/src/linkedlist.h b/src/linkedlist.h index b1cc8c3..b43e9ba 100644 --- a/src/linkedlist.h +++ b/src/linkedlist.h @@ -15,6 +15,7 @@ typedef struct playerSkill playerSkill; // -=[ Data Structures ]=-: // ======================== +// An enum of the possible data types that can be stored in a list: typedef enum listDataType { PATH, @@ -23,6 +24,7 @@ typedef enum listDataType SKILL } listDataType; +// A union containing a pointers to all data types that can be stored in a list: typedef union listData { playerPath * path; @@ -30,7 +32,8 @@ typedef union listData playerInfo * player; playerSkill * skill; } listData; - + +// A doubly linked node for the linked list type: typedef struct listNode listNode; typedef struct listNode { @@ -39,6 +42,7 @@ typedef struct listNode listNode * previous; } listNode; +// A header structure for the list containing the length, head, tail, and type of the list. typedef struct list { listDataType type; diff --git a/src/playerdata.c b/src/playerdata.c index 3add36e..56bb4ed 100644 --- a/src/playerdata.c +++ b/src/playerdata.c @@ -11,7 +11,7 @@ // Create a new skill and add it to the global skill list: listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) { - if(skillNameLength >= 32) + if (skillNameLength >= 32) { fprintf(stderr, "Skill name is too long. Please shorten the name and try again.\n"); return NULL; @@ -35,9 +35,9 @@ int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, pla // Check if the skill exists in the game: size_t globalIndex = 0; bool skillExists = false; - while(globalIndex < globalSkillList->itemCount) + while (globalIndex < globalSkillList->itemCount) { - if(strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) == 0) + if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) == 0) { skillExists = true; break; @@ -45,7 +45,7 @@ int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, pla globalIndex++; } - if(!skillExists) + if (!skillExists) { fprintf(stderr, "Skill doesn't exist in skill list.\n"); return -1; @@ -54,16 +54,16 @@ int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, pla // Check if the player has the skill: size_t playerIndex = 0; bool playerHasSkill = false; - while(playerIndex < targetPlayer->skills->itemCount) + while (playerIndex < targetPlayer->skills->itemCount) { - if(strncmp(skillName, getFromList(targetPlayer->skills, playerIndex)->skill->skillName, skillNameLength) == 0) + if (strncmp(skillName, getFromList(targetPlayer->skills, playerIndex)->skill->skillName, skillNameLength) == 0) { playerHasSkill = true; break; } playerIndex++; } - if(playerHasSkill) + if (playerHasSkill) { getFromList(targetPlayer->skills, playerIndex)->skill->skillPoints++; } @@ -85,7 +85,7 @@ int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, pla coreStat getCoreStatFromString(char * inputString, int stringLength) { // Check we've got a long enough string to fit a stat: - if(stringLength < 4) + if (stringLength < 4) { return INVALID; } @@ -99,11 +99,11 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) // If we have a string that's at most just the stat name plus a null character, or // a dirtier string, we can check in a better order and ignore impossibilites: - if(stringLength < 9) + if (stringLength < 9) { - if(stringLength <= 4) + if (stringLength <= 4) { - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; @@ -115,19 +115,19 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) } } // Hopefully one of the seven letter long ones: - else if(stringLength <= 7) + else if (stringLength <= 7) { - if(strncmp(string, "strength", 7) == 0) + if (strncmp(string, "strength", 7) == 0) { free(string); return STRENGTH; } - else if(strncmp(string, "dexerity", 7) == 0) + else if (strncmp(string, "dexerity", 7) == 0) { free(string); return DEXERITY; } - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; @@ -141,27 +141,27 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) // Hopefully one of the 8 letter long stats: else { - if(strncmp(string, "intellect", 8) == 0) + if (strncmp(string, "intellect", 8) == 0) { free(string); return INTELLECT; } - else if(strncmp(string, "endurance", 8) == 0) + else if (strncmp(string, "endurance", 8) == 0) { free(string); return ENDURANCE; } - else if(strncmp(string, "strength", 7) == 0) + else if (strncmp(string, "strength", 7) == 0) { free(string); return STRENGTH; } - else if(strncmp(string, "dexerity", 7) == 0) + else if (strncmp(string, "dexerity", 7) == 0) { free(string); return DEXERITY; } - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; @@ -176,27 +176,27 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) // Worst case, it's definitely a dirty string, compare them all: else { - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; } - else if(strncmp(string, "intellect", 8) == 0) + else if (strncmp(string, "intellect", 8) == 0) { free(string); return INTELLECT; } - else if(strncmp(string, "strength", 7) == 0) + else if (strncmp(string, "strength", 7) == 0) { free(string); return STRENGTH; } - else if(strncmp(string, "endurance", 8) == 0) + else if (strncmp(string, "endurance", 8) == 0) { free(string); return ENDURANCE; } - else if(strncmp(string, "dexerity", 7) == 0) + else if (strncmp(string, "dexerity", 7) == 0) { free(string); return DEXERITY; @@ -209,6 +209,7 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) } } +// Deallocate a player's information including the skill lists and stats: int deallocatePlayer(playerInfo * playerToDeallocate) { // Deallocate the skill list: diff --git a/src/playerdata.h b/src/playerdata.h index 8d0ae0c..f703682 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -14,6 +14,7 @@ typedef struct playerPath playerPath; typedef struct listNode listNode; typedef struct list list; +// The basic information that needs to be stored for a player or creature's stats: typedef struct statBlock { // Levelling: @@ -36,6 +37,7 @@ typedef struct statBlock int skillPoints; } statBlock; +// Information about a skill, including skill levels and modifiers for the player: typedef struct playerSkill { char skillName[32]; @@ -44,6 +46,7 @@ typedef struct playerSkill bool trainedSkill; } playerSkill; +// Information about a single player's character: typedef struct playerInfo { char playerName[32]; @@ -52,6 +55,7 @@ typedef struct playerInfo list * skills; } playerInfo; +// An enum of the main stats of the game: typedef enum coreStat { WITS, @@ -72,7 +76,7 @@ int takeSkillbyID(list * globalSkillList, int skillID, playerInfo * targetPlayer // Take a string containing a core stat name and return the core stat: coreStat getCoreStatFromString(char * string, int stringLength); -// Deallocate a player: +// Deallocate a player's information including the skill lists and stats: int deallocatePlayer(playerInfo * playerToDeallocate); #endif diff --git a/src/texteffects.c b/src/texteffects.c index 34328fc..7cdaf57 100644 --- a/src/texteffects.c +++ b/src/texteffects.c @@ -5,10 +5,11 @@ #include #include -void slowPrint(char * stringToPrint, int delay) +// A character by character print, similar to a serial terminal with lower baud rate: +void slowPrint(const char * stringToPrint, int delay) { int characterIndex = 0; - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { putchar(stringToPrint[characterIndex]); // Flush the buffer so there's no line buffering. @@ -18,14 +19,15 @@ void slowPrint(char * stringToPrint, int delay) } } -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) +// The same, altered to work with ncurses: +void slowPrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded) { int characterIndex = 0; - if(bolded) + if (bolded) { wattron(window, A_BOLD); } - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { waddch(window, stringToPrint[characterIndex]); // Refresh the ncurses screen. @@ -33,17 +35,18 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bol usleep(delay); characterIndex++; } - if(bolded) + if (bolded) { wattroff(window, A_BOLD); } wrefresh(window); } -void bruteforcePrint(char * stringToPrint, int delay) +// A character by character "brute-force" print, similar to Hollywood hacking scenes: +void bruteforcePrint(const char * stringToPrint, int delay) { unsigned int characterIndex = 0; - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { for(unsigned char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++) { @@ -58,14 +61,15 @@ void bruteforcePrint(char * stringToPrint, int delay) } } -void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) +// The same, altered to work with ncurses: +void bruteforcePrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded) { int characterIndex = 0; - if(bolded) + if (bolded) { wattron(window, A_BOLD); } - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { for(char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++) { @@ -78,19 +82,20 @@ void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bo waddch(window, stringToPrint[characterIndex]); characterIndex++; } - if(bolded) + if (bolded) { wattroff(window, A_BOLD); } wrefresh(window); } +// Word-wrap a string to a given width: void wrapString(char * stringToWrap, int stringLength, int screenWidth) { int characterCount = 0; for(int index = 0; index < stringLength; index++) { - if(stringToWrap[index] == '\n') + if (stringToWrap[index] == '\n') { characterCount = 0; } @@ -98,13 +103,13 @@ void wrapString(char * stringToWrap, int stringLength, int screenWidth) { characterCount++; } - if(characterCount == screenWidth) + if (characterCount == screenWidth) { - while(!isspace(stringToWrap[index]) && index > 0) + while (!isspace(stringToWrap[index]) && index > 0) { index--; } - if(index == 0) + if (index == 0) { return; } diff --git a/src/texteffects.h b/src/texteffects.h index 0d0305b..37292c4 100644 --- a/src/texteffects.h +++ b/src/texteffects.h @@ -5,20 +5,23 @@ #include #include -// A character by character print, similar to a serial terminal with lower baud rate. -void slowPrint(char * stringToPrint, int delay); +// A character by character print, similar to a serial terminal with lower baud rate: +void slowPrint(const char * stringToPrint, int delay); -// The same, altered to work with ncurses. -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); +// The same, altered to work with ncurses: +void slowPrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded); -// A character by character "brute-force" print, similar to Hollywood hacking scenes. -void bruteforcePrint(char * stringToPrint, int delay); +// A character by character "brute-force" print, similar to Hollywood hacking scenes: +void bruteforcePrint(const char * stringToPrint, int delay); -// The same, altered to work with ncurses. -void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); +// The same, altered to work with ncurses: +void bruteforcePrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded); + +// Word-wrap a string to a given width: +void wrapString(char * stringToWrap, int stringLength, int screenWidth); // A string containing an ASCII art version of the Silverkin Industries logo. -char * logostring = +const char * logostring = " ///////\n" " //////////////////////////////////////////\n" " ///////////////////////////////////////////////////////////\n" @@ -32,6 +35,4 @@ char * logostring = " # # # # # # # # ## # ### # # ## //\n" " # # ### ##### ##### ### # # # # #### ### /\n"; -void wrapString(char * stringToWrap, int stringLength, int screenWidth); - #endif From ff281e5ce6b9a74158a1c5aa97b7e429727b1018 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Feb 2023 22:16:12 +0000 Subject: [PATCH 48/53] Increment version number --- src/client/SilverMUDClient.c | 4 ++-- src/server/SilverMUDServer.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index 8f12c42..2822f68 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.4. +// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.5. // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -181,7 +181,7 @@ int main(int argc, char ** argv) bool chatLogging = false, gameLogging = false; // Print welcome message: - slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.4\n", 5000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.5\n", 5000); // Parse command-line options: while ((currentopt = getopt(argc, argv, "i:c:g:p:d:")) != -1) diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index fe63799..51aee27 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.4. +// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.5 // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -122,7 +122,7 @@ int main(int argc, char ** argv) // Give an intro: Display the Silverkin Industries logo and splash text. slowPrint(logostring, delay); - slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.4\n", delay); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.5\n", delay); // Seed random number generator from the current time: srandom((unsigned)time(¤tTime)); From b61520b9989b95d3a06c2edbe4903efd26d50861 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Thu, 16 Feb 2023 23:08:18 +0000 Subject: [PATCH 49/53] Add non-functional stub for talk. - Added a message upon running '/talk'. This allows me to see if the command is actually executed. --- src/gamelogic.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index fdb0d2b..5743cee 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -297,7 +297,20 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) // Talk command: Allows the player to begin a chat session with another player: if (strncmp(currentCommand->command, "talk", 4) == 0) { - // TODO: Implement. + userMessage * talkMessage = malloc(sizeof(userMessage)); + talkMessage->senderName[0] = '\0'; + + // Temporary message until we can implement objects, events, and challenges. + strcpy(talkMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n"); + + // Allocate an outputMessage for the queue: + outputMessage * talkOutputMessage = createTargetedOutputMessage(talkMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, talkOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage: + free(talkMessage); } // Stat command: Displays the current character's sheet. if (strncmp(currentCommand->command, "stat", 4) == 0) From 9dabdcfba7f5d9dd432d7353b5dfbf49043194dc Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Thu, 16 Feb 2023 23:15:02 +0000 Subject: [PATCH 50/53] Oops, wrong word. Changed "try" to "talk." --- src/gamelogic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index 5743cee..1929ff8 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -301,7 +301,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) talkMessage->senderName[0] = '\0'; // Temporary message until we can implement objects, events, and challenges. - strcpy(talkMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n"); + strcpy(talkMessage->messageContent, "The talk command is currently not implemented. Implement it if you want to use it.\n"); // Allocate an outputMessage for the queue: outputMessage * talkOutputMessage = createTargetedOutputMessage(talkMessage, ¤tCommand->caller, 1); From e6a13ed2acb8709552525669007375693ff02419 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Fri, 17 Feb 2023 15:05:49 +0000 Subject: [PATCH 51/53] Added a MOTD command-line option to SilverMUD Server. - Added a message of the day option, -m, to SilverMUDServer. - Adjusted the character delay to 800 in SilverMUDServer. --- src/server/SilverMUDServer.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 51aee27..a1dfe49 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -36,7 +36,7 @@ void sigintHandler(int signal) int main(int argc, char ** argv) { time_t currentTime; - unsigned delay = 4000; + unsigned delay = 800; int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; @@ -46,19 +46,25 @@ int main(int argc, char ** argv) playerInfo connectedPlayers[PLAYERCOUNT]; char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; + char motd[2048] = "Please login with the /join command."; queue * inputQueue = createQueue(), * outputQueue = createQueue(); - + // Parse command-line options: int currentopt = 0; - while ((currentopt = getopt(argc, argv, "d:")) != -1) + while ((currentopt = getopt(argc, argv, "d:m:")) != -1) { switch(currentopt) { - case 'd': - { - delay = atoi(optarg); - break; - } + case 'd': + { + delay = atoi(optarg); + break; + } + case 'm': + { + strncpy(motd, optarg, strlen(optarg) + 1); + break; + } } } @@ -68,7 +74,7 @@ int main(int argc, char ** argv) // -==[ TEST GAME-STATE INITIALIZATION ]==- // Initialize test areas: list * areas = createList(AREA); - addToList(areas, createArea("Login Area", "Please login with the /join command."), AREA); + addToList(areas, createArea("Login Area", motd), AREA); // Create the areas: addToList(areas, createArea("Octal One - Docking Bay Alpha", From afedf15c63750b9da205b6c0d96e6a7d18865d91 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 19 Feb 2023 00:32:03 +0000 Subject: [PATCH 52/53] Updated the evaluateNextCommand to use a hash instead of strncmp - Added hashCommand. - Refactored evaluateNextCommand to use hashCommand to jump to the correct functionality with a switch. --- src/gamelogic.c | 761 +++++++++++++++++++++++++----------------------- src/gamelogic.h | 3 + 2 files changed, 406 insertions(+), 358 deletions(-) diff --git a/src/gamelogic.c b/src/gamelogic.c index 1929ff8..a612ea9 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -108,7 +108,7 @@ void queueMessagedCommand(queue * queue, inputMessage * messageToQueue) // Copy the command and arguments to the new commandEvent: memcpy(newCommand->command, &messageToQueue->content->messageContent[1], 16); memcpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2], - MAX - (strlen(newCommand->command) + 2)); + MAX - (strlen(newCommand->command) + 2)); // Ensure the arguments are safe to parse, without adding newlines: userNameSanatize(newCommand->command, 16); @@ -158,410 +158,442 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) { return -1; } - // Try command: Attempt to use a stat or skill on an object: - if (strncmp(currentCommand->command, "try", 3) == 0) + + // Hash the command and execute the relevant functionality: + switch (hashCommand(currentCommand->command, strlen(currentCommand->command))) { - userMessage * tryMessage = malloc(sizeof(userMessage)); - tryMessage->senderName[0] = '\0'; - - // Temporary message until we can implement objects, events, and challenges. - strcpy(tryMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n"); - - // Allocate an outputMessage for the queue: - outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, tryOutputMessage, OUTPUT_MESSAGE); - - // Free the userMessage: - free(tryMessage); - } - // Exit command: Sends an "empty" exit message to disconnect a client: - if (strncmp(currentCommand->command, "exit", 4) == 0) - { - // Allocate a userMessage containing null characters as the first char in both fields: - userMessage * exitMessage = malloc(sizeof(userMessage)); - exitMessage->senderName[0] = '\0'; - exitMessage->messageContent[0] = '\0'; - - // Allocate an outputMessage for the queue: - outputMessage * exitOutputMessage = createTargetedOutputMessage(exitMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, exitOutputMessage, OUTPUT_MESSAGE); - - // Free the userMessage - free(exitMessage); - } - - // Move command: Moves the caller to a different area given a path name or number: - if (strncmp(currentCommand->command, "move", 4) == 0) - { - char requestedPath[32]; - if (strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area) + // Look command: Returns the description of the current area and paths: + case 5626697: { - memcpy(requestedPath, currentCommand->arguments, 32); - userNameSanatize(requestedPath, 32); - requestedPath[31] = '\0'; - if (movePlayerToArea(currentCommand->caller, requestedPath) == 0) - { - // Call the look command after moving. It's fine to unlock, because the loop won't - // continue until the command is queued: - queue->lock = false; - queueCommand(queue, "look", "", 5, 0, currentCommand->caller); - queue->lock = true; - } - } - } + char formattedString[64]; + userMessage * lookMessage = calloc(1, sizeof(userMessage)); + lookMessage->senderName[0] = '\0'; + strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33); + strncat(lookMessage->messageContent, "\n", 2); + strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35); - // Look command: Returns the description of the current area and paths: - if (strncmp(currentCommand->command, "look", 4) == 0) - { - char formattedString[64]; - userMessage * lookMessage = calloc(1, sizeof(userMessage)); - lookMessage->senderName[0] = '\0'; - strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33); - strncat(lookMessage->messageContent, "\n", 2); - strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35); + // Allocate an outputMessage for the queue: + outputMessage * lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); - // Allocate an outputMessage for the queue: - outputMessage * lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); - - //queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); - bzero(lookMessage, sizeof(userMessage)); - - // Loop through the paths and send the appropriate amount of messages: - int charCount = 13; - strncat(lookMessage->messageContent, "You can go:", 13); - - if (currentCommand->caller->currentArea->pathList->itemCount > 0) - { - for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++) - { - if ((charCount + 64) >= MAX) - { - lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); - - bzero(lookMessage, sizeof(userMessage)); - charCount = 0; - } - snprintf(formattedString, 64, "\n\t%ld. %s", index + 1, - getFromList(currentCommand->caller->currentArea->pathList, index)->path->pathName); - strncat(lookMessage->messageContent, formattedString, 64); - charCount += 64; - } - // Allocate another outputMessage for the queue: - lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); - // Queue the outputMessage: pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); - } - free(lookMessage); - } - // Join command: Allows the player to join the game given a name: - // TODO: Implement login/character creation. Will be a while: - if (strncmp(currentCommand->command, "join", 4) == 0) - { - if (currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area) - { - bool validName = true; - for(int index = 0; index < *parameters->playerCount; index++) + + //queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + bzero(lookMessage, sizeof(userMessage)); + + // Loop through the paths and send the appropriate amount of messages: + int charCount = 13; + strncat(lookMessage->messageContent, "You can go:", 13); + + if (currentCommand->caller->currentArea->pathList->itemCount > 0) { - if (currentCommand->arguments[0] == '\0') + for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++) { - validName = false; + if ((charCount + 64) >= MAX) + { + lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); + + bzero(lookMessage, sizeof(userMessage)); + charCount = 0; + } + snprintf(formattedString, 64, "\n\t%ld. %s", index + 1, + getFromList(currentCommand->caller->currentArea->pathList, index)->path->pathName); + strncat(lookMessage->messageContent, formattedString, 64); + charCount += 64; + } + // Allocate another outputMessage for the queue: + lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); + } + free(lookMessage); + + break; + } + + // Stat command: Displays the current character's sheet. + case 5987604: + { + char * formattedString = calloc(121, sizeof(char)); + userMessage * statMessage = calloc(1, sizeof(userMessage)); + statMessage->senderName[0] = '\0'; + // Basic status: Name, level, location. + snprintf(formattedString, 120, "%s, Level %d | %s\n", currentCommand->caller->playerName, + currentCommand->caller->stats->level, currentCommand->caller->currentArea->areaName); + strncat(statMessage->messageContent, formattedString, 120); + + // Current stats: Health and WISED. + snprintf(formattedString, 120, + "Health: %d/%d\nStats:\n\tWits: %2d | Intellect: %2d | Strength: %2d | Endurance: %2d | Dexerity: %2d \n", + currentCommand->caller->stats->currentHealth, currentCommand->caller->stats->maxHealth, + currentCommand->caller->stats->wits, currentCommand->caller->stats->intellect, + currentCommand->caller->stats->strength, currentCommand->caller->stats->endurance, + currentCommand->caller->stats->dexerity); + strncat(statMessage->messageContent, formattedString, 120); + + // Levelling stats: Current XP, and spec points. + if (currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) + { + snprintf(formattedString, 120, "Current Experience: %ld | Spec Points Available: %d | Skill Points Available: %d", + currentCommand->caller->stats->experience, currentCommand->caller->stats->specPoints, currentCommand->caller->stats->skillPoints); + } + else + { + snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience); + } + strncat(statMessage->messageContent, formattedString, 120); + + // Allocate an outputMessage for the queue: + outputMessage * statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); + + bzero(statMessage->messageContent, sizeof(char) * MAX); + if (currentCommand->caller->skills->head != NULL) + { + size_t skillIndex = 0; + int charCount = 0; + bool addNewline = false; + playerSkill * skill; + while (skillIndex < currentCommand->caller->skills->itemCount) + { + skill = getFromList(currentCommand->caller->skills, skillIndex)->skill; + skillIndex++; + snprintf(formattedString, 120, "| %2d | %31s ", skill->skillPoints, skill->skillName); + charCount += 43; + strncat(statMessage->messageContent, formattedString, 120); + if ((charCount + 43) >= MAX) + { + // Allocate an outputMessage for the queue: + statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); + bzero(statMessage, sizeof(userMessage)); + charCount = 0; + break; + } + else if (addNewline) + { + strncat(statMessage->messageContent, "|\n", 3); + charCount++; + addNewline = false; + } + else + { + addNewline = true; + } } - if (strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) + // Allocate an outputMessage for the queue: + statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); + } + free(statMessage); + free(formattedString); + + break; + } + + // Spec command: Assign spec points to stats: + case 5982259: + { + userMessage * specMessage = calloc(1, sizeof(userMessage)); + specMessage->senderName[0] = '\0'; + char * formattedString = calloc(121, sizeof(char)); + if (currentCommand->caller->stats->specPoints > 0) + { + int selectedAmount = 0; + strtok(currentCommand->arguments, " "); + selectedAmount = atoi(¤tCommand->arguments[strlen(currentCommand->arguments) + 1]); + coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16); + if (selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) { - validName = false; + switch (selectedStat) + { + case WITS: + { + currentCommand->caller->stats->wits += selectedAmount; + strncat(specMessage->messageContent, "Increased wits.", 16); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case INTELLECT: + { + currentCommand->caller->stats->intellect += selectedAmount; + strncat(specMessage->messageContent, "Increased intellect.", 21); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case STRENGTH: + { + currentCommand->caller->stats->strength += selectedAmount; + strncat(specMessage->messageContent, "Increased strength.", 20); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case ENDURANCE: + { + currentCommand->caller->stats->endurance += selectedAmount; + strncat(specMessage->messageContent, "Increased endurance.", 21); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case DEXERITY: + { + currentCommand->caller->stats->dexerity += selectedAmount; + strncat(specMessage->messageContent, "Increased dexerity.", 21); + currentCommand->caller->stats->specPoints -= selectedAmount; + break; + } + case INVALID: + { + strncat(specMessage->messageContent, "Invalid stat.", 21); + } + } + } + else + { + strncat(specMessage->messageContent, "You have entered an invalid amount of spec points.", 51); } } - if (validName) + else { - strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16); - currentCommand->caller->currentArea = getFromList(parameters->areaList, 1)->area; - // Call the look command after joining. It's fine to unlock, because the loop won't - // continue until the command is queued: - queue->lock = false; - queueCommand(queue, "look", "", 5, 0, currentCommand->caller); - queue->lock = true; + strncat(specMessage->messageContent, "You have no spec points available.", 35); } - } - } - // Talk command: Allows the player to begin a chat session with another player: - if (strncmp(currentCommand->command, "talk", 4) == 0) - { - userMessage * talkMessage = malloc(sizeof(userMessage)); - talkMessage->senderName[0] = '\0'; - - // Temporary message until we can implement objects, events, and challenges. - strcpy(talkMessage->messageContent, "The talk command is currently not implemented. Implement it if you want to use it.\n"); - // Allocate an outputMessage for the queue: - outputMessage * talkOutputMessage = createTargetedOutputMessage(talkMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, ¤tCommand->caller, 1); - // Queue the outputMessage: - pushQueue(parameters->outputQueue, talkOutputMessage, OUTPUT_MESSAGE); + // Queue the outputMessage: + pushQueue(parameters->outputQueue, specOutputMessage, OUTPUT_MESSAGE); - // Free the userMessage: - free(talkMessage); - } - // Stat command: Displays the current character's sheet. - if (strncmp(currentCommand->command, "stat", 4) == 0) - { - char * formattedString = calloc(121, sizeof(char)); - userMessage * statMessage = calloc(1, sizeof(userMessage)); - statMessage->senderName[0] = '\0'; - // Basic status: Name, level, location. - snprintf(formattedString, 120, "%s, Level %d | %s\n", currentCommand->caller->playerName, - currentCommand->caller->stats->level, currentCommand->caller->currentArea->areaName); - strncat(statMessage->messageContent, formattedString, 120); - - // Current stats: Health and WISED. - snprintf(formattedString, 120, - "Health: %d/%d\nStats:\n\tWits: %2d | Intellect: %2d | Strength: %2d | Endurance: %2d | Dexerity: %2d \n", - currentCommand->caller->stats->currentHealth, currentCommand->caller->stats->maxHealth, - currentCommand->caller->stats->wits, currentCommand->caller->stats->intellect, - currentCommand->caller->stats->strength, currentCommand->caller->stats->endurance, - currentCommand->caller->stats->dexerity); - strncat(statMessage->messageContent, formattedString, 120); - - // Levelling stats: Current XP, and spec points. - if (currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) - { - snprintf(formattedString, 120, "Current Experience: %ld | Spec Points Available: %d | Skill Points Available: %d", - currentCommand->caller->stats->experience, currentCommand->caller->stats->specPoints, currentCommand->caller->stats->skillPoints); - } - else - { - snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience); - } - strncat(statMessage->messageContent, formattedString, 120); - - // Allocate an outputMessage for the queue: - outputMessage * statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); + // Show the new stat sheet: + queue->lock = false; + queueCommand(queue, "stat", "", 5, 0, currentCommand->caller); + queue->lock = true; - bzero(statMessage->messageContent, sizeof(char) * MAX); - if (currentCommand->caller->skills->head != NULL) - { - size_t skillIndex = 0; + // Free the finished message: + free(specMessage); + free(formattedString); + + break; + } + + // Try command: Attempt to use a stat or skill on an object: + case 163143: + { + // Allocate the userMessage to send: + userMessage * tryMessage = malloc(sizeof(userMessage)); + tryMessage->senderName[0] = '\0'; + + // Temporary message until we can implement objects, events, and challenges. + strcpy(tryMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n"); + + // Allocate an outputMessage for the queue: + outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, tryOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage: + free(tryMessage); + + break; + } + + // Move command: Moves the caller to a different area given a path name or number: + case 5677603: + { + char requestedPath[32]; + if (strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area) + { + memcpy(requestedPath, currentCommand->arguments, 32); + userNameSanatize(requestedPath, 32); + requestedPath[31] = '\0'; + if (movePlayerToArea(currentCommand->caller, requestedPath) == 0) + { + // Call the look command after moving. It's fine to unlock, because the loop won't + // continue until the command is queued: + queue->lock = false; + queueCommand(queue, "look", "", 5, 0, currentCommand->caller); + queue->lock = true; + } + } + + break; + } + + // Skill command: Allows you to put skill points into skills: + case 221096235: + { + userMessage * skillMessage = calloc(1, sizeof(userMessage)); + skillMessage->senderName[0] = '\0'; + if ((currentCommand->caller->stats->skillPoints - 1) >= 0) + { + int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments, + strlen(currentCommand->arguments), currentCommand->caller); + switch(returnValue) + { + case -1: + { + strcpy(skillMessage->messageContent, "Not a valid skill."); + break; + } + case 0: + { + strcpy(skillMessage->messageContent, "Took "); + strcat(skillMessage->messageContent, currentCommand->arguments); + strcat(skillMessage->messageContent, "."); + currentCommand->caller->stats->skillPoints--; + break; + } + } + } + else + { + strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n"); + } + + // Allocate an outputMessage for the queue: + outputMessage * skillOutputMessage = createTargetedOutputMessage(skillMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, skillOutputMessage, OUTPUT_MESSAGE); + + free(skillMessage); + break; + } + + // Listskills commands: List all available skills on the server: + case 2395990522: + { + userMessage * listMessage = calloc(1, sizeof(userMessage)); + char * formattedString = calloc(121, sizeof(char)); int charCount = 0; + size_t skillIndex = 0; bool addNewline = false; - playerSkill * skill; - while (skillIndex < currentCommand->caller->skills->itemCount) + playerSkill * currentSkill; + while (skillIndex < parameters->globalSkillList->itemCount) { - skill = getFromList(currentCommand->caller->skills, skillIndex)->skill; - skillIndex++; - snprintf(formattedString, 120, "| %2d | %31s ", skill->skillPoints, skill->skillName); + currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill; + snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName); charCount += 43; - strncat(statMessage->messageContent, formattedString, 120); - if ((charCount + 43) >= MAX) + strncat(listMessage->messageContent, formattedString, 120); + if ((charCount + 46) >= MAX) { // Allocate an outputMessage for the queue: - statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); - + outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); + // Queue the outputMessage: - pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); - bzero(statMessage, sizeof(userMessage)); + pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); + + bzero(listMessage, sizeof(userMessage)); charCount = 0; - break; + addNewline = false; } else if (addNewline) { - strncat(statMessage->messageContent, "|\n", 3); + strncat(listMessage->messageContent, "|\n", 3); charCount++; addNewline = false; } else { addNewline = true; - } + } + skillIndex++; } // Allocate an outputMessage for the queue: - statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); + free(listMessage); + free(formattedString); + break; + } + + // Talk command: Allows the player to begin a chat session with another player: + case 601264: + { + userMessage * talkMessage = malloc(sizeof(userMessage)); + talkMessage->senderName[0] = '\0'; + + // Temporary message until we can implement objects, events, and challenges. + strcpy(talkMessage->messageContent, "The talk command is currently not implemented. Implement it if you want to use it.\n"); + + // Allocate an outputMessage for the queue: + outputMessage * talkOutputMessage = createTargetedOutputMessage(talkMessage, ¤tCommand->caller, 1); // Queue the outputMessage: - pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); - } - free(statMessage); - free(formattedString); - } - - // Spec command: Assign spec points to stats: - if (strncmp(currentCommand->command, "spec", 4) == 0) - { - userMessage * specMessage = calloc(1, sizeof(userMessage)); - specMessage->senderName[0] = '\0'; - char * formattedString = calloc(121, sizeof(char)); - if (currentCommand->caller->stats->specPoints > 0) - { - int selectedAmount = 0; - strtok(currentCommand->arguments, " "); - selectedAmount = atoi(¤tCommand->arguments[strlen(currentCommand->arguments) + 1]); - coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16); - if (selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) - { - switch (selectedStat) - { - case WITS: - { - currentCommand->caller->stats->wits += selectedAmount; - strncat(specMessage->messageContent, "Increased wits.", 16); - currentCommand->caller->stats->specPoints -= selectedAmount; - break; - } - case INTELLECT: - { - currentCommand->caller->stats->intellect += selectedAmount; - strncat(specMessage->messageContent, "Increased intellect.", 21); - currentCommand->caller->stats->specPoints -= selectedAmount; - break; - } - case STRENGTH: - { - currentCommand->caller->stats->strength += selectedAmount; - strncat(specMessage->messageContent, "Increased strength.", 20); - currentCommand->caller->stats->specPoints -= selectedAmount; - break; - } - case ENDURANCE: - { - currentCommand->caller->stats->endurance += selectedAmount; - strncat(specMessage->messageContent, "Increased endurance.", 21); - currentCommand->caller->stats->specPoints -= selectedAmount; - break; - } - case DEXERITY: - { - currentCommand->caller->stats->dexerity += selectedAmount; - strncat(specMessage->messageContent, "Increased dexerity.", 21); - currentCommand->caller->stats->specPoints -= selectedAmount; - break; - } - case INVALID: - { - strncat(specMessage->messageContent, "Invalid stat.", 21); - } - } - } - else - { - strncat(specMessage->messageContent, "You have entered an invalid amount of spec points.", 51); - } - } - else - { - strncat(specMessage->messageContent, "You have no spec points available.", 35); + pushQueue(parameters->outputQueue, talkOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage: + free(talkMessage); + + break; } - // Allocate an outputMessage for the queue: - outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, specOutputMessage, OUTPUT_MESSAGE); - - // Show the new stat sheet: - queue->lock = false; - queueCommand(queue, "stat", "", 5, 0, currentCommand->caller); - queue->lock = true; - - // Free the finished message: - free(specMessage); - free(formattedString); - } - if (strncmp(currentCommand->command, "skill", 5) == 0) - { - userMessage * skillMessage = calloc(1, sizeof(userMessage)); - skillMessage->senderName[0] = '\0'; - if ((currentCommand->caller->stats->skillPoints - 1) >= 0) + // Exit command: Sends an "empty" exit message to disconnect a client: + case 5284234: { - int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments, - strlen(currentCommand->arguments), currentCommand->caller); - switch(returnValue) + // Allocate a userMessage containing null characters as the first char in both fields: + userMessage * exitMessage = malloc(sizeof(userMessage)); + exitMessage->senderName[0] = '\0'; + exitMessage->messageContent[0] = '\0'; + + // Allocate an outputMessage for the queue: + outputMessage * exitOutputMessage = createTargetedOutputMessage(exitMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, exitOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage + free(exitMessage); + + break; + } + + // Join command: Allows the player to join the game given a name: + // TODO: Implement login/character creation. Will be a while: + case 5525172: + { + if (currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area) { - case -1: + bool validName = true; + for(int index = 0; index < *parameters->playerCount; index++) { - strcpy(skillMessage->messageContent, "Not a valid skill."); - break; + if (currentCommand->arguments[0] == '\0') + { + validName = false; + } + if (strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) + { + validName = false; + } } - case 0: + if (validName) { - strcpy(skillMessage->messageContent, "Took "); - strcat(skillMessage->messageContent, currentCommand->arguments); - strcat(skillMessage->messageContent, "."); - currentCommand->caller->stats->skillPoints--; - break; + strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16); + currentCommand->caller->currentArea = getFromList(parameters->areaList, 1)->area; + // Call the look command after joining. It's fine to unlock, because the loop won't + // continue until the command is queued: + queue->lock = false; + queueCommand(queue, "look", "", 5, 0, currentCommand->caller); + queue->lock = true; } } + break; } - else - { - strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n"); - } + } - // Allocate an outputMessage for the queue: - outputMessage * skillOutputMessage = createTargetedOutputMessage(skillMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, skillOutputMessage, OUTPUT_MESSAGE); - - free(skillMessage); - } - if (strncmp(currentCommand->command, "listskills", 10) == 0) - { - userMessage * listMessage = calloc(1, sizeof(userMessage)); - char * formattedString = calloc(121, sizeof(char)); - int charCount = 0; - size_t skillIndex = 0; - bool addNewline = false; - playerSkill * currentSkill; - while (skillIndex < parameters->globalSkillList->itemCount) - { - currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill; - snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName); - charCount += 43; - strncat(listMessage->messageContent, formattedString, 120); - if ((charCount + 46) >= MAX) - { - // Allocate an outputMessage for the queue: - outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); - - bzero(listMessage, sizeof(userMessage)); - charCount = 0; - addNewline = false; - } - else if (addNewline) - { - strncat(listMessage->messageContent, "|\n", 3); - charCount++; - addNewline = false; - } - else - { - addNewline = true; - } - skillIndex++; - } - // Allocate an outputMessage for the queue: - outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); - - // Queue the outputMessage: - pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); - free(listMessage); - free(formattedString); - } // Remove the current command and unlock the queue: currentCommand = NULL; queue->lock = false; @@ -719,7 +751,7 @@ int movePlayerToArea(playerInfo * player, char * requestedPath) if (selected != 0 && !(selected > player->currentArea->pathList->itemCount)) { if (getFromList(player->currentArea->pathList, selected - 1)->path != NULL && - getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL) + getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL) { player->currentArea = getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin; return 0; @@ -734,12 +766,25 @@ int movePlayerToArea(playerInfo * player, char * requestedPath) for (size_t index = 0; index < player->currentArea->pathList->itemCount; index++) { if (strncmp(getFromList(player->currentArea->pathList, index)->path->pathName, - requestedPath, 32) == 0) + requestedPath, 32) == 0) { - printf("%s: %s\n", player->playerName, getFromList(player->currentArea->pathList, index)->path->pathName); player->currentArea = getFromList(player->currentArea->pathList, index)->path->areaToJoin; return 0; } } return 1; } + +// A hash function for distinguishing commands for the game logic handler: +unsigned int hashCommand(char * command, unsigned int commandLength) +{ + unsigned int hash = 0; + char * currentCharacter = command; + + for (unsigned int index = 0; index < commandLength && *currentCharacter != '\0'; currentCharacter++) + { + hash = 37 * hash + *currentCharacter; + } + + return hash; +} diff --git a/src/gamelogic.h b/src/gamelogic.h index 1e717c5..2fd9b8d 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -55,6 +55,9 @@ int evaluateNextCommand(gameLogicParameters * parameters, queue * queue); void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer); +// A hash function for distinguishing commands for the game logic handler: +unsigned int hashCommand(char * command, unsigned int commandLength); + // ============================ // -=[ Gameplay Primitives ]=-: // ============================ From caa792de8d47efb4189a6dbc84bd73d9e66d33e9 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Sun, 19 Feb 2023 11:28:16 +0000 Subject: [PATCH 53/53] Added the AGPLv3 as the license, and renamed SilverMUD.org. --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++ SilverMUD.org => README.org | 0 2 files changed, 661 insertions(+) create mode 100644 LICENSE rename SilverMUD.org => README.org (100%) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/SilverMUD.org b/README.org similarity index 100% rename from SilverMUD.org rename to README.org