diff --git a/Makefile b/Makefile index 0bccd36..90805f5 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,29 @@ 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 -SERVERLDFLAGS= -lncurses +CLIENTLDFLAGS= -lpthread -lncurses -lgnutls +SERVERLDFLAGS= -lpthread -lncurses -lgnutls SilverMUDClient: $(clientobj) - gcc -o $@ $^ $(CLIENTLDFLAGS) + gcc -o $@ $^ $(CLIENTLDFLAGS) SilverMUDServer: $(serverobj) - gcc -o $@ $^ $(SERVERLDFLAGS) + gcc -o $@ $^ $(SERVERLDFLAGS) + +SilverMUDClientDebug: $(clientobj) + gcc -pg $^ $(CLIENTLDFLAGS) -o $@ + +SilverMUDServerDebug: $(serverobj) + gcc -pg $^ $(SERVERLDFLAGS) -o $@ .PHONY: clean clean: - rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer + rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug -all: SilverMUDClient SilverMUDServer +all: clean SilverMUDClient SilverMUDServer +all: CFLAGS += -Wall -Wextra -Ofast +debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og +debug: clean SilverMUDClientDebug SilverMUDServerDebug diff --git a/SilverMUD.org b/SilverMUD.org new file mode 100644 index 0000000..070bfe8 --- /dev/null +++ b/SilverMUD.org @@ -0,0 +1,28 @@ +* 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 +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/src/SilverMUDClient.c b/src/SilverMUDClient.c deleted file mode 100644 index f64bb66..0000000 --- a/src/SilverMUDClient.c +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "misc/playerdata.h" -#include "misc/texteffects.h" -#include "misc/inputhandling.h" -#define MAX 1024 -#define PORT 5000 -#define SA struct sockaddr - -// 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 (!shouldExit) - { - bzero(sendBuffer, MAX); - wprintw(threadParameters->window, "\n\n\nCOMM-LINK> "); - if(wgetnstr(threadParameters->window, sendBuffer, MAX) == ERR) - { - // Quit if there's any funny business with getting input: - pthread_exit(NULL); - } - userInputSanatize(sendBuffer, MAX); - if(sendBuffer[0] == '\n') - { - continue; - } - write(threadParameters->socketDescriptor, sendBuffer, MAX); - } - 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; - while (!shouldExit) - { - 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); -} - -int main(int argc, char **argv) -{ - int sockfd, connfd; - 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.3\n", 5000); - - // Give me a socket, and make sure it's working: - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd == -1) - { - printf("Socket creation failed.\n"); - exit(0); - } - else - { - slowPrint("Socket successfully created.\n", 8000); - } - bzero(&servaddr, sizeof(servaddr)); - - // 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); - - // 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); - exit(0); - } - else - { - slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); - } - usleep(100000); - - // Setup Ncurses: - initscr(); - - // Create two pointers to structs to pass arguments to the threads: - threadparameters * logArea; - threadparameters * messageArea; - - 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); - - // 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 deleted file mode 100644 index c2bde11..0000000 --- a/src/SilverMUDServer.c +++ /dev/null @@ -1,223 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include -#include -#include "misc/playerdata.h" -#include "misc/texteffects.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; - char receiveBuffer[MAX]; - fd_set connectedClients; - 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.3\n", 5000); - - // Initialize the sockets to 0, so we don't crash. - for (int index = 0; index < maxClients; index++) - { - clientSockets[index] = 0; - } - - // Get a socket and make sure we actually get one. - socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); - if (socketFileDesc == -1) - { - perror("Socket creation is \033[33;40mRED.\033[0m Aborting launch.\n"); - exit(0); - } - - else - { - slowPrint(" Socket creation is \033[32;40mGREEN.\033[0m\n", 5000); - } - - bzero(&serverAddress, sizeof(serverAddress)); - - // Assign IP and port: - serverAddress.sin_family = AF_INET; - serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); - serverAddress.sin_port = htons(PORT); - - // 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"); - exit(0); - } - else - { - slowPrint(" Socket binding is \033[32;40mGREEN.\033[0m\n", 5000); - } - - // Let's start listening: - if ((listen(socketFileDesc, 64)) != 0) - { - perror("Server listening is \033[33;40mRED.\033[0m Aborting launch.\n"); - exit(0); - } - else - { - slowPrint(" Server listening is \033[32;40mGREEN.\033[0m\n", 5000); - } - length = sizeof(clientAddress); - - //connectionFileDesc = accept(socketFileDesc, (sockaddr*)&clientAddress, &length); - // Accept the data packet from client and verification - while (1) - { - FD_ZERO(&connectedClients); - FD_SET(socketFileDesc, &connectedClients); - clientsAmount = socketFileDesc; - bzero(receiveBuffer, sizeof(receiveBuffer)); - for (int i = 0; i < maxClients; i++) - { - // Just get the one we're working with to another name: - socketCheck = clientSockets[i]; - - // If it's working, bang it into the list: - if(socketCheck > 0) - { - FD_SET(socketCheck, &connectedClients); - } - // The amount of clients is needed for select(): - if(socketCheck > clientsAmount) - { - clientsAmount = socketCheck; - } - } - - // See if a connection is ready to be interacted with: - activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); - - // Check if select() worked: - if ((activityCheck < 0) && (errno != EINTR)) - { - perror("Error in select(), retrying.\n"); - } - - // 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) - { - 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++) - { - // When there is an empty slot, pop it in: - if( clientSockets[i] == 0 ) - { - clientSockets[i] = connectionFileDesc; - printf("Adding to list of sockets as %d.\n" , i); - break; - } - } - } - else - { - // Otherwise, it's a client socket to be interacted with: - for (int i = 0; i < maxClients; i++) - { - 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)); - 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)); - - // 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: %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], sendBuffer.senderName, sizeof(sendBuffer.senderName)); - write(clientSockets[sendIndex], sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); - } - } - bzero(sendBuffer.senderName, sizeof(sendBuffer.senderName)); - bzero(sendBuffer.messageContent, sizeof(sendBuffer.messageContent)); - } - } - } - } - } -} diff --git a/src/areadata.c b/src/areadata.c new file mode 100644 index 0000000..cdab489 --- /dev/null +++ b/src/areadata.c @@ -0,0 +1,217 @@ +// 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)); + newAreaList->data = initialArea; + newAreaList->next = NULL; + newAreaList->prev = NULL; + 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) +{ + 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; +} + +// 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) +{ + areaNode * current = fromList; + for(int index = 0; index < listIndex; index++) + { + if(current->next != NULL) + { + current = current->next; + } + else + { + return NULL; + } + } + 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) +{ + areaNode * current = getAreaNode(fromList, listIndex); + return current->data; +} diff --git a/src/areadata.h b/src/areadata.h new file mode 100644 index 0000000..53da6e6 --- /dev/null +++ b/src/areadata.h @@ -0,0 +1,89 @@ +// areadata.h: Contains data structures and functions for playerAreas and playerPaths in SilverMUD: +// Barra Ó Catháin, 2022. +#ifndef AREADATA_H +#define AREADATA_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; + +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); */ + +/* int savePathList(pathNode * listToSave); */ + +/* int loadAreaList(areaNode * listToLoad); */ + +/* int loadPathList(pathNode * listToLoad); */ + +#endif diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c new file mode 100644 index 0000000..b0a8c50 --- /dev/null +++ b/src/client/SilverMUDClient.c @@ -0,0 +1,333 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../constants.h" +#include "../playerdata.h" +#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: +typedef struct threadparameters +{ + gnutls_session_t tlsSession; + FILE * loggingStream; + bool loggingFlag; + WINDOW * window; +} threadparameters; + +// Use sockaddr as a type: +typedef struct sockaddr sockaddr; + +// A globally available exit boolean: +bool shouldExit = false; + +void * messageSender(void * parameters) +{ + 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) + { + // Quit if there's any funny business with getting input: + pthread_exit(NULL); + } + + // Ignore empty messages: + if (sendBuffer.messageContent[0] == '\n') + { + continue; + } + + // 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); + } + + // Send the message off to the server: + messageSend(threadParameters->tlsSession, &sendBuffer); + } + + // Rejoin the main thread: + pthread_exit(NULL); +} + +void * messageReceiver(void * parameters) +{ + 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); + // 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); + if (receiveBuffer.messageContent[0] == '\0') + { + shouldExit = true; + pthread_exit(NULL); + } + 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); + fputs(": ", threadParameters->loggingStream); + fputs(receiveBuffer.messageContent, threadParameters->loggingStream); + fflush(threadParameters->loggingStream); + } + 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); +} + +int main(int argc, char ** argv) +{ + int socketFileDesc; + struct sockaddr_in serverAddress; + pthread_t sendingThread; + pthread_t receivingThread; + int port = 5000; + int currentopt = 0; + int characterDelay = 4000; + 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.4\n", 5000); + + // Parse command-line options: + while ((currentopt = getopt(argc, argv, "i:c:g:p:d:")) != -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 'p': + { + port = atoi(optarg); + break; + } + case 'd': + { + characterDelay = atoi(optarg); + break; + } + case '?': + { + return 1; + break; + } + } + } + + // Give me a socket, and make sure it's working: + socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); + if (socketFileDesc == -1) + { + printf("Socket creation failed.\n"); + exit(EXIT_FAILURE); + } + else + { + slowPrint("Socket successfully created.\n", characterDelay); + } + bzero(&serverAddress, sizeof(serverAddress)); + + // 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); + + // Connect the server and client sockets, Kronk: + 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); + } + 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); + } + + // 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, 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; + + 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; + logArea->loggingFlag = chatLogging; + if (chatLog != NULL) + { + logArea->loggingStream = chatLog; + } + 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; + } + + // Set the two windows to scroll: + scrollok(logArea->window, true); + scrollok(messageArea->window, true); + + // Run a thread to send messages, and use another to recieve: + pthread_create(&sendingThread, NULL, messageSender, messageArea); + pthread_create(&receivingThread, NULL, messageReceiver, logArea); + + // Wait for /EXIT: + pthread_join(receivingThread, NULL); + + // Close the threads: + pthread_cancel(sendingThread); + + // Close the session and socket: + gnutls_bye(tlsSession, GNUTLS_SHUT_WR); + close(socketFileDesc); + + // Free the structs: + free(logArea); + free(messageArea); + + // Close the log files: + if (gameLog != NULL) + { + fclose(gameLog); + } + if (chatLog != NULL) + { + fclose(chatLog); + } + + // Unsetup Ncurses: + endwin(); + + // Say goodbye: + slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", characterDelay); +} + diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..bb598d8 --- /dev/null +++ b/src/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/gamelogic.c b/src/gamelogic.c new file mode 100644 index 0000000..112291f --- /dev/null +++ b/src/gamelogic.c @@ -0,0 +1,829 @@ +// gamelogic.c: Contains function definitons for dealing with the game's logic. +// Barry Kane, 2022. +#include +#include +#include +#include +#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; + commandQueue * commandQueue = createCommandQueue(); + while(true) + { + // 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; + 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] == '/') + { + queueMessagedCommand(commandQueue, currentInput); + } + else if(currentInput->sender->currentArea == getAreaFromList(threadParameters->areaList, 0)) + { + currentInput = NULL; + threadParameters->inputQueue->lock = false; + dequeueInputMessage(threadParameters->inputQueue); + } + 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); + } + } + 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) +{ + // 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: + { + sprintf(tryMessage->messageContent,"%d", + skillCheck(currentCommand->caller, 10, currentCommand->arguments, strlen(currentCommand->arguments), + parameters->globalSkillList)); + 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) +{ + // Calculate the chance: + if(chance > 100 || chance < 0) + { + return ERROR; + } + chance = 100 - chance; + + // Calculate the modifier: + 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; + } + } +} + +// 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) +{ + // 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 new file mode 100644 index 0000000..7660063 --- /dev/null +++ b/src/gamelogic.h @@ -0,0 +1,95 @@ +// 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 "areadata.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 ]=-: +// ============================ + +// Player movement: +int movePlayerToArea(playerInfo * player, char * requestedPath); + +typedef enum outcome +{ + CRITICAL_FAILURE, + FAILURE, + SUCCESS, + CRITICAL_SUCCESS, + ERROR +} outcome; + +// Run a stat check: +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); + +#endif diff --git a/src/inputoutput.c b/src/inputoutput.c new file mode 100644 index 0000000..e6af436 --- /dev/null +++ b/src/inputoutput.c @@ -0,0 +1,357 @@ +// inputoutput.c: Implementation of input/output library for SilverMUD. +// Barry Kane, 2022. +#include +#include +#include +#include +#include +#include +#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) +{ + 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); + + return returnValue; +} + +// Recieves a message from a given TLS session, wraps the calls to gnutls_read: +int 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); + + 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 * 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); + + // 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 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'; +} + +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) +{ + return queue->front; +} + +outputMessage * peekOutputMessage(outputMessageQueue * queue) +{ + return queue->front; +} diff --git a/src/inputoutput.h b/src/inputoutput.h new file mode 100644 index 0000000..177fac0 --- /dev/null +++ b/src/inputoutput.h @@ -0,0 +1,107 @@ +// 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 +#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[MAX]; +} userMessage; + +// ================== +// -=[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); + +// 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 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); + +// ================== +// -=[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); + +// Sanatize user names so they display correctly; +void userNameSanatize(char * inputString, int length); + +#endif + diff --git a/src/misc/inputhandling.c b/src/misc/inputhandling.c deleted file mode 100644 index 0f4824b..0000000 --- a/src/misc/inputhandling.c +++ /dev/null @@ -1,16 +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; - } - } -} 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 diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h deleted file mode 100644 index 4cf1d8c..0000000 --- a/src/misc/playerdata.h +++ /dev/null @@ -1,17 +0,0 @@ -// 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; - - - diff --git a/src/misc/texteffects.c b/src/misc/texteffects.c deleted file mode 100644 index 875b3cc..0000000 --- a/src/misc/texteffects.c +++ /dev/null @@ -1,32 +0,0 @@ -// texteffects.c: Implementation of text effect library for SilverMUD. -// Barry Kane, 2021. -#include -#include -#include - -void slowPrint(char * stringToPrint, int delay) -{ - int characterIndex = 0; - while(stringToPrint[characterIndex] != '\0') - { - putchar(stringToPrint[characterIndex]); - // Flush the buffer so there's no line buffering. - fflush(stdout); - usleep(delay); - characterIndex++; - } -} - -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window) -{ - int characterIndex = 0; - while(stringToPrint[characterIndex] != '\0') - { - waddch(window, stringToPrint[characterIndex]); - // Refresh the ncurses screen. - wrefresh(window); - usleep(delay); - characterIndex++; - } - wrefresh(window); -} diff --git a/src/misc/texteffects.h b/src/misc/texteffects.h deleted file mode 100644 index d75e92c..0000000 --- a/src/misc/texteffects.h +++ /dev/null @@ -1,17 +0,0 @@ -// texteffects.h: Header file for the texteffects library for SilverMUD. -// Barry Kane, 2021. -#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, 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"; - -#endif diff --git a/src/playerdata.c b/src/playerdata.c new file mode 100644 index 0000000..50f32e0 --- /dev/null +++ b/src/playerdata.c @@ -0,0 +1,295 @@ +// playerdata.c: Contains functions definitions for working with player data. +// Barry Kane, 2021 +#include +#include +#include +#include +#include +#include "constants.h" +#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) +{ + 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..01564b2 --- /dev/null +++ b/src/playerdata.h @@ -0,0 +1,91 @@ +// 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 "areadata.h" +#include "constants.h" + +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; + +// 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 new file mode 100644 index 0000000..824de8d --- /dev/null +++ b/src/server/SilverMUDServer.c @@ -0,0 +1,338 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../areadata.h" +#include "../gamelogic.h" +#include "../constants.h" +#include "../playerdata.h" +#include "../texteffects.h" +#include "../inputoutput.h" + +typedef struct sockaddr sockaddr; +void sigintHandler(int signal) +{ + exit(EXIT_SUCCESS); +} + +int main(int argc, char ** argv) +{ + time_t currentTime; + int socketFileDesc, connectionFileDesc, length, clientsAmount, + socketCheck, activityCheck, returnVal; + fd_set connectedClients; + pthread_t gameLogicThread; + int clientSockets[PLAYERCOUNT]; + userMessage sendBuffer, receiveBuffer; + playerInfo connectedPlayers[PLAYERCOUNT]; + char testString[32] = "Hehe."; + 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.")); + 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.4\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++) + { + clientSockets[index] = 0; + } + + // Get a socket and make sure we actually get one. + socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); + if (socketFileDesc == -1) + { + fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + exit(0); + } + + else + { + slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000); + } + + // + bzero(&serverAddress, sizeof(serverAddress)); + + // Assign IP and port: + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(PORT); + + // Binding newly created socket to given IP, and checking it works: + if ((bind(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress))) != 0) + { + fprintf(stderr, "\tSocket Binding is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + exit(0); + } + + else + { + slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", 5000); + } + + // Let's start listening: + if ((listen(socketFileDesc, PLAYERCOUNT)) != 0) + { + fprintf(stderr, "\tServer Listener is:\t\033[33;40mRED.\033[0m Aborting launch.\n"); + exit(EXIT_FAILURE); + } + else + { + 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." + for (int index = 0; index < PLAYERCOUNT; index++) + { + tlssessions[index] = NULL; + if (gnutls_init(&tlssessions[index], GNUTLS_SERVER) < 0) + { + 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 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(true) + { + // Clear the set of file descriptors angad add the master socket: + FD_ZERO(&connectedClients); + FD_SET(socketFileDesc, &connectedClients); + clientsAmount = socketFileDesc; + + // 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[index]; + + // If it's working, bang it into the list: + if(socketCheck > 0) + { + FD_SET(socketCheck, &connectedClients); + } + // The amount of clients is needed for select(): + if(socketCheck > clientsAmount) + { + clientsAmount = socketCheck; + } + } + + // See if a connection is ready to be interacted with: + activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout); + + // Check if select() worked: + if ((activityCheck < 0) && (errno != EINTR)) + { + fprintf(stderr, "Error in select(), retrying.\n"); + } + + // 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) + { + fprintf(stderr, "Failed to accept connection. Aborting.\n"); + exit(EXIT_FAILURE); + } + // See if we can put in the client: + for (int index = 0; index < PLAYERCOUNT; index++) + { + // When there is an empty slot, pop it in: + if (clientSockets[index] == 0) + { + 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); + + // 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; + } + } + } + // Otherwise, it's a client we need to interact with: + else + { + for (int index = 0; index < PLAYERCOUNT; index++) + { + socketCheck = clientSockets[index]; + + 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); + } + // Otherwise, they've sent a message: + else + { + queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); + } + } + } + } + + // 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++) + { + 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); + } + } + pthread_cancel(gameLogicThread); + exit(EXIT_SUCCESS); +} + diff --git a/src/texteffects.c b/src/texteffects.c new file mode 100644 index 0000000..34328fc --- /dev/null +++ b/src/texteffects.c @@ -0,0 +1,116 @@ +// texteffects.c: Implementation of text effect library for SilverMUD. +// Barry Kane, 2021. +#include +#include +#include +#include + +void slowPrint(char * stringToPrint, int delay) +{ + int characterIndex = 0; + while(stringToPrint[characterIndex] != '\0') + { + putchar(stringToPrint[characterIndex]); + // Flush the buffer so there's no line buffering. + fflush(stdout); + usleep(delay); + characterIndex++; + } +} + +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]); + // Refresh the ncurses screen. + wrefresh(window); + usleep(delay); + characterIndex++; + } + if(bolded) + { + wattroff(window, A_BOLD); + } + wrefresh(window); +} + +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) + { + 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/src/texteffects.h b/src/texteffects.h new file mode 100644 index 0000000..060c99c --- /dev/null +++ b/src/texteffects.h @@ -0,0 +1,36 @@ +// texteffects.h: Header file for the texteffects library for SilverMUD. +// Barry Kane, 2021. +#ifndef TEXTEFFECTS_H_ +#define TEXTEFFECTS_H_ +#include +#include + +// 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. +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"; + +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 new file mode 100644 index 0000000..9fbeb6a --- /dev/null +++ b/tests/list-test.c @@ -0,0 +1,24 @@ +#include "../src/lists.h" +#include "../src/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; + } +} diff --git a/tests/outputQueue-test.c b/tests/outputQueue-test.c new file mode 100644 index 0000000..a36fd43 --- /dev/null +++ b/tests/outputQueue-test.c @@ -0,0 +1,35 @@ +#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; +}