Alpha 0.4 release of SilverMUD:

Features Added:
- Encryption via GnuTLS.
- Basic gameplay commands.
- Basic character sheet with stats and skills.
- Ability to perform chance-based checks with stats and skills.
- Ability to build a character using spec points and skill points.

Features Changed:
- Messaging and communication is now encrypted.
- Area descriptions can now be longer.
- General bug-fixing, see individual commits.
- Makefile improved to improve development experience.
- Naming system altered to only allow naming on joining the game.
- Server messages are now displayed differently than user messages and are grouped.
- Commands are now evaluated in a queue.

Features Removed:
- Ability to change name via /NAME.
- Graceful handling of C-c. (It's seemingly unreliable, so not graceful.)

Squashed commit of the following:

commit ca8ba5e410
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Oct 30 13:00:18 2022 +0000

    Incremented version numbering in preperation for merge to master.

    - Incremented version numbering to Alpha 0.4.

commit d9497679cb
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Oct 30 12:58:39 2022 +0000

    Implemented Skill Checks.

    - Implemented skillCheck.
    - Allowed for the in-game testing of skillCheck via /try.
    - Slightly reorganized the Makefile.
    - Tweaked the logoString to display correctly.
    - Edited the client and server to generate gprof data when in debug builds.

commit f2dd83857f
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Oct 23 17:07:13 2022 +0100

    Completed Reorganization of Area Data

    - Moved the appropriate data structures and functions into areaData.
    - Made movePlayerToArea a gameplay primitive.

commit 52b4b1e2f0
Author: Barry Kane <barry@omnimenu.ie>
Date:   Tue Oct 18 21:00:57 2022 +0100

    Warning fixes and Makefile edits

    - Adjusted the Makefile to create gprof data in the server (currently inactive as the server never terminates.)
    - Fixed warnings in areadata.c and gamelogic.c.
    - Added bruteforcePrint for completeness.

commit 60110d3abd
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Oct 16 21:28:32 2022 +0100

    Made client exit gracefully upon server exit:

    - The client now checks the return value of messageReceive.
    - Renamed lists.c/.h to areadata.c/.h.

commit b8189ae2de
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Oct 16 16:13:33 2022 +0100

    Began implementation of skills and stats.

    - Added text wrapping in client.
    - Implemented functions for managing skill data.
    - Rewrote some existing functionality to allow for variable length game messages over multiple userMessages.
    - Reorganized the code yet again.
    - Introduced enums for coreStats and outcomes.
    - Implemented core stat checks.
    - Added more example skills.
    - Rewrote test areas to have longer descriptions.

commit 8673bb1ad5
Author: Barry Kane <barry@omnimenu.ie>
Date:   Fri May 20 22:28:07 2022 +0100

    Reorganized file structure.

    - Reimplemented /LOOK.
    - Commands are now accepted in both upper and lower case.
    - Move now accepts a number for easier movement.

commit 151f3002b8
Author: Barry Kane <barry@omnimenu.ie>
Date:   Thu May 5 19:45:27 2022 +0100

    Began implementing game logic and re-implementing commands

    - Reimplemented /MOVE and /EXIT
    - The server is now multi-threaded
    - Input and output is now queued

commit 0b3a72beff
Author: Barry <bazzakane@gmail.com>
Date:   Thu Apr 7 01:39:59 2022 +0100

    Removed inputhandling library

    The functionality was moved to inputoutput.

commit 4ddb80b8b2
Author: Barry <bazzakane@gmail.com>
Date:   Thu Apr 7 01:38:36 2022 +0100

    Basic message queuing implemented

    - Messages are now queued on reception by the server.
    - Message queue datastructures are now added.

commit e4b8693037
Author: Barry <barry@omnimenu.ie>
Date:   Tue Mar 15 14:52:49 2022 +0000

    Cleaned up client.

    - Cleaned up the client codebase.
    - Throughly commented SilverMUDClient.c.
    - Added a boolean for bolding slowPrintNcurses.
    - Added a user-configurable delay for text printing.
    - Other small improvements.

commit 5d772df469
Author: Barry <barry@omnimenu.ie>
Date:   Sun Mar 6 00:36:42 2022 +0000

    Added initial GnuTLS encryption.

    - Added inputoutput.c
    - Added inputoutput.h
    - inputoutput contains wrapper and helper functions for transmitting messages over GnuTLS.
    - Moved the userMessage struct definition to inputoutput.
    - Reworked client and server to use GnuTLS.
    - Removed all commands from server in preperation for upcoming command and message queues.
    - Names and areas are no longer considered for messaging.
    - Changed Makefile to link GnuTLS.

commit 235ff8e74f
Author: Barry <barry@omnimenu.ie>
Date:   Sun Dec 26 19:07:30 2021 +0000

    Added basic logging support and command-line options to the client.

    - Added basic logging support to the client.
    - Added basic command-line options to the client:
      -g: Enables a game-log, must have a file-path.
      -c: Enables a chat-log, must have a file-path.
      -i: Sets the IP address to connect to.
    - Removed the C-c handler, appeared to be broken anyways. Consider
      reimplementation at some point.
    - Added /EXIT command to allow for leaving the game.
    - The client now exits gracefully if the server dies.

commit 6c93805d6f
Author: Barry <barry@omnimenu.ie>
Date:   Sun Dec 5 23:33:53 2021 +0000

    Added initial implementation of doubly-linked lists

    - Added lists.c
    - Added lists.h
    - Changed initialisation of rooms to add a third room and to add the rooms to a
      list.
    - Added datastructures for area and path nodes for doubly-linked lists.

commit 241ac7a92b
Author: Barry <barry@omnimenu.ie>
Date:   Thu Nov 4 23:14:47 2021 +0000

    Added area and path creation functions

    - Added missing header guards.
    - Increased the size of message contents to 2048.
    - Added area and path initialization functions.
    - movePlayerToArea no longer segfaults.
    - /LOOK added to allow players to find exits.
    - Amount of paths allowed out of an area has been decreased to 16.
    - Debug builds are now available from the Makefile.
    - Removed unused variables.
    - Input sanatization has been moved to the server-side, phew.
    - Server messages are now displayed differently to player messages.
    - New area initialization has been added until I can integrate Guile.
    - Server's sendBuffer has been renamed messageBuffer.
    - Areas now have descriptions.
    - Descriptions are sent to the player upon joining an area and /LOOK-ing.

commit 85a31a2933
Author: Barry <barry@omnimenu.ie>
Date:   Thu Oct 21 21:58:55 2021 +0100

    Added basic area system
    - Added playerdata.c
    - Added basic move command
    - Added a basic initialisation of two connected rooms
    - Added datastructures for areas and paths

commit ae8373d4ce
Author: Barry Kane <bazzakane@gmail.com>
Date:   Wed Sep 15 00:12:05 2021 +0100

    Incremented Version Number.

    - Incremented version number in preperation for merge.

commit 18a4f416f6
Author: Barry Kane <bazzakane@gmail.com>
Date:   Wed Sep 15 00:07:13 2021 +0100

    Added basic name system

    - Added basic name system.
    - Added playerdata.h.
    - Added basic /NAME command. TODO: Create proper command system.
    - Added datastructures for user messages and user names.

commit 9411803942
Author: Barry Kane <bazzakane@gmail.com>
Date:   Fri Sep 10 15:07:42 2021 +0100

    Increment version message for merge.

    Incremented the version number by 0.1 for the server.
    Added version splash to the client.

commit 7047d0ee08
Author: Barry Kane <bazzakane@gmail.com>
Date:   Fri Sep 10 15:03:02 2021 +0100

    Added two-window messaging to the client.

    Client now has two seperate Ncurses windows for sending and receiving.
    Added SIGINT handler which sets a global boolean to gracefully exit and free memory.
    Sending and Receiving are now on their own threads.
    A pointer-to-struct is now passed to the threads.
    The main thread will now wait to cancel the threads upon receiving SIGINT.
    slowPrintNcurses now takes a window argument.
    The server now doesn't check that a client receives the message that they sent, allowing for full chat history.

commit 33bc9bcda0
Author: Barry Kane <bazzakane@gmail.com>
Date:   Fri Sep 3 18:47:11 2021 +0100

    Adapted client to use Ncurses instead of raw terminal output:
    Created "slowPrintNcurses", which is a version of "slowPrint" compatible with Ncurses screens.
    Ncurses is now used in place of raw-terminal output.
    The screen clears after inital start-up messages.
    C-d no longer exits, and still doesn't spam.
    Added Ncurses to the ld options of client in the Makefile.
    Created ld options for server in the Makefile.

commit 849a80bd37
Author: Barry Kane <barry@omnimenu.ie>
Date:   Thu Aug 19 23:07:58 2021 +0100

    Basic input sanatization:
    Created new library to deal with user input.
    Implemented check in client to prevent C-d spamming the server.
    C-d now exits.
    Implemented check in client to prevent clients sending messages containing only newlines
    to the server.

commit 2c093903a4
Author: Barry Kane <barry@omnimenu.ie>
Date:   Tue Aug 17 18:57:56 2021 +0100

    Git Sanity Check
This commit is contained in:
Barry Kane 2022-10-30 13:31:27 +00:00
parent 52c0fed848
commit fa46e40860
25 changed files with 3029 additions and 500 deletions

View File

@ -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)
SilverMUDServer: $(serverobj)
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

28
SilverMUD.org Normal file
View File

@ -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

View File

@ -1,175 +0,0 @@
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <ncurses.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#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);
}

View File

@ -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 <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ncurses.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#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));
}
}
}
}
}
}

217
src/areadata.c Normal file
View File

@ -0,0 +1,217 @@
// areadata.c: Implements functions for playerAreas and playerPaths in SilverMUD:
// Barra Ó Catháin, 2022.
#include <string.h>
#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;
}

89
src/areadata.h Normal file
View File

@ -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

View File

@ -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 <netdb.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <pthread.h>
#include <ncurses.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <gnutls/gnutls.h>
#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);
}

9
src/constants.h Normal file
View File

@ -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

829
src/gamelogic.c Normal file
View File

@ -0,0 +1,829 @@
// gamelogic.c: Contains function definitons for dealing with the game's logic.
// Barry Kane, 2022.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#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, &currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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(&currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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, &currentCommand->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;
}

95
src/gamelogic.h Normal file
View File

@ -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

357
src/inputoutput.c Normal file
View File

@ -0,0 +1,357 @@
// inputoutput.c: Implementation of input/output library for SilverMUD.
// Barry Kane, 2022.
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <gnutls/gnutls.h>
#include "constants.h"
#include "playerdata.h"
#include "inputoutput.h"
// Sends a message to a given TLS session, wraps the calls to gnutls_write:
int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend)
{
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;
}

107
src/inputoutput.h Normal file
View File

@ -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 <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include "constants.h"
#include "playerdata.h"
#include <gnutls/gnutls.h>
// 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

View File

@ -1,16 +0,0 @@
// inputhandling.c: Implementation of input handling library for SilverMUD.
// Barry Kane, 2021.
#include <ctype.h>
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;
}
}
}

View File

@ -1,10 +0,0 @@
// inputhandling.h: Header file for the inputhandling library for SilverMUD.
// Barry Kane, 2021
#ifndef INPUTHANDLING_H
#define INPUTHANDLING_H
#include <ctype.h>
// Sanatize user input to ensure it's okay to send to the server:
void userInputSanatize(char * inputString, int length);
#endif

View File

@ -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;

View File

@ -1,32 +0,0 @@
// texteffects.c: Implementation of text effect library for SilverMUD.
// Barry Kane, 2021.
#include <stdio.h>
#include <unistd.h>
#include <ncurses.h>
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);
}

View File

@ -1,17 +0,0 @@
// texteffects.h: Header file for the texteffects library for SilverMUD.
// Barry Kane, 2021.
#ifndef TEXTEFFECTS_H_
#define TEXTEFFECTS_H_
#include <stdio.h>
#include <ncurses.h>
// 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

295
src/playerdata.c Normal file
View File

@ -0,0 +1,295 @@
// playerdata.c: Contains functions definitions for working with player data.
// Barry Kane, 2021
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#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;
}

91
src/playerdata.h Normal file
View File

@ -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 <stdlib.h>
#include <stdbool.h>
#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

View File

@ -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 <time.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <ncurses.h>
#include <pthread.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <gnutls/gnutls.h>
#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(&currentTime));
// 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);
}

116
src/texteffects.c Normal file
View File

@ -0,0 +1,116 @@
// texteffects.c: Implementation of text effect library for SilverMUD.
// Barry Kane, 2021.
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <ncurses.h>
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;
}
}
}

36
src/texteffects.h Normal file
View File

@ -0,0 +1,36 @@
// texteffects.h: Header file for the texteffects library for SilverMUD.
// Barry Kane, 2021.
#ifndef TEXTEFFECTS_H_
#define TEXTEFFECTS_H_
#include <stdio.h>
#include <ncurses.h>
// 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

View File

@ -0,0 +1,11 @@
// Hopefully optimized corestat to string:
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../src/playerdata.h"
void main(int argc, char ** argv)
{
getCoreStatFromString(argv[1], strlen(argv[1]));
}

24
tests/list-test.c Normal file
View File

@ -0,0 +1,24 @@
#include "../src/lists.h"
#include "../src/playerdata.h"
#include <stdio.h>
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;
}
}

35
tests/outputQueue-test.c Normal file
View File

@ -0,0 +1,35 @@
#include "../src/inputoutput.h"
#include <stdio.h>
#include <string.h>
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;
}