diff --git a/.gitignore b/.gitignore
index 6d5206b..7d9403b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,63 @@
modules.order
Module.symvers
Mkfile.old
-dkms.conf
\ No newline at end of file
+dkms.conf
+
+# http://www.gnu.org/software/automake
+
+Makefile.in
+/ar-lib
+/mdate-sh
+/py-compile
+/test-driver
+/ylwrap
+.deps/
+.dirstamp
+
+# http://www.gnu.org/software/autoconf
+
+autom4te.cache
+/autoscan.log
+/autoscan-*.log
+/aclocal.m4
+/compile
+/config.cache
+/config.guess
+/config.h.in
+/config.log
+/config.status
+/config.sub
+/configure
+/configure.scan
+/depcomp
+/install-sh
+/missing
+/stamp-h1
+
+# https://www.gnu.org/software/libtool/
+
+/ltmain.sh
+
+# http://www.gnu.org/software/texinfo
+
+/texinfo.tex
+
+# http://www.gnu.org/software/m4/
+
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
+
+# Generated Makefile
+# (meta build system like autotools,
+# can automatically generate from config.status script
+# (which is called by configure script))
+Makefile
+
+SilverMUDServer
+SilverMUDClient
+config.h
+config.h.in
+stamp-h1
\ No newline at end of file
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..6f59a34
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,17 @@
+bin_PROGRAMS = SilverMUDServer SilverMUDClient
+dist_doc_DATA = README.org
+SilverMUDServer_CFLAGS = -lgnutls -g $(GUILE_CFLAGS) $(GUILE_LIBS)
+SilverMUDClient_CFLAGS = -lgnutls -g -lncurses $(GUILE_CFLAGS) $(GUILE_LIBS)
+
+SilverMUDServer_SOURCES = \
+ source/messages.c \
+ source/server/player-data.c \
+ source/server/connections.c \
+ source/server/scheme-integration.c \
+ source/server/main.c
+
+SilverMUDClient_SOURCES = \
+ source/messages.c \
+ source/client/client-drawing.c \
+ source/client/receiving-thread.c \
+ source/client/main.c
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..1151bc2
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,9 @@
+AC_INIT([SilverMUD], [0.0.1], [barra@ocathain.ie])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
+AC_PROG_CC
+AC_CONFIG_HEADERS([source/config.h])
+AC_CONFIG_FILES([
+ Makefile
+])
+PKG_CHECK_MODULES([GUILE], [guile-3.0])
+AC_OUTPUT
diff --git a/documentation/.placeholder b/documentation/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/documentation/SilverMUD-Gamemasters-Guidebook.org b/documentation/SilverMUD-Gamemasters-Guidebook.org
new file mode 100644
index 0000000..b3facaf
--- /dev/null
+++ b/documentation/SilverMUD-Gamemasters-Guidebook.org
@@ -0,0 +1,9 @@
+#+TITLE: SilverMUD: Gamemaster's Guidebook
+#+SUBTITLE: SilverMUD First Edition
+#+AUTHOR: Barra Ó Catháin
+
+* How To Run SilverMUD, The Software:
+
+* How To Run SilverMUD, The Game:
+
+* Creating Content:
diff --git a/documentation/SilverMUD-Players-Guidebook.org b/documentation/SilverMUD-Players-Guidebook.org
new file mode 100644
index 0000000..cfea4ff
--- /dev/null
+++ b/documentation/SilverMUD-Players-Guidebook.org
@@ -0,0 +1,15 @@
+#+TITLE: SilverMUD: Player's Guidebook
+#+SUBTITLE: SilverMUD First Edition
+#+AUTHOR: Barra Ó Catháin
+
+* Getting Started:
+
+* Basic Commands:
+
+* Interacting With The World:
+
+* The Character System:
+
+* The Combat System:
+
+* Commands In Depth:
diff --git a/documentation/SilverMUD-Programmers-Guidebook.org b/documentation/SilverMUD-Programmers-Guidebook.org
new file mode 100644
index 0000000..3120ea4
--- /dev/null
+++ b/documentation/SilverMUD-Programmers-Guidebook.org
@@ -0,0 +1,10 @@
+#+TITLE: SilverMUD: Programmer's Guidebook
+#+SUBTITLE: SilverMUD First Edition
+#+AUTHOR: Barra Ó Catháin
+
+* The Scheme Programming Language:
+
+* The Basic Concepts Of SilverMUD Programming:
+
+* Concepts In Detail:
+
diff --git a/notes/.placeholder b/notes/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/notes/SilverMUD-Implementation.org b/notes/SilverMUD-Implementation.org
new file mode 100644
index 0000000..113fc46
--- /dev/null
+++ b/notes/SilverMUD-Implementation.org
@@ -0,0 +1,28 @@
+#+TITLE: SilverMUD Implementation Document
+This document contains information about various implementation details of
+SilverMUD, as a scratchpad for decisions before implementation.
+
+* Structures:
+** Server->Client Message Format (1409 bytes total):
+*** Message Type - Unsigned 8 Bit Integer:
+|-------+------------------------|
+| Value | Purpose |
+|-------+------------------------|
+| 0 | System Message |
+| 1 | Client Setting Message |
+| 2 | Command Output Message |
+| 3 | Local Chat Message |
+| 4 | Player Chat Message |
+| 5 | Party Chat Message |
+| 6 | Player Emote Message |
+|-------+------------------------|
+
+Further values remain open for possible additional message types.
+
+*** Sender Name - 128 Character String.
+This field may be repurposed for message types without a need for a name.
+
+*** Message Content - 1024 Character String.
+
+** Client->Server Message Format (1024 bytes total):
+*** Message Content - 1024 Character String.
diff --git a/source/.placeholder b/source/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/source/client/.placeholder b/source/client/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/source/client/client-drawing.c b/source/client/client-drawing.c
new file mode 100644
index 0000000..faa21da
--- /dev/null
+++ b/source/client/client-drawing.c
@@ -0,0 +1,61 @@
+// ==========================================
+// | SilverMUD Client - client-drawing.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// ==========================================
+#include "../config.h"
+#include "client-drawing.h"
+
+void redrawClientLayout(WINDOW * gameWindow, WINDOW * chatWindow, WINDOW * inputWindow)
+{
+ int height, width;
+ getmaxyx(stdscr, height, width);
+
+ // Draw the lines that will seperate windows:
+ wborder(stdscr, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
+ attron(A_REVERSE);
+ mvwhline(stdscr, 0, 0, '=', width);
+ mvwhline(stdscr, height / 2, 0, '=', width);
+ mvwhline(stdscr, height - 2, 0, '=', width);
+
+ // Write the labels for windows:
+ attron(COLOR_PAIR(1));
+ mvwprintw(stdscr, 0, 1, " SilverMUD | Version %s ", PACKAGE_VERSION);
+ mvwprintw(stdscr, height / 2, 1, " Chat ");
+ mvwprintw(stdscr, height - 2, 1, " Input ");
+ attroff(COLOR_PAIR(1));
+ attroff(A_REVERSE);
+
+ // Move the windows into place:
+ mvwin(gameWindow, 1, 1);
+ mvwin(chatWindow, (height / 2) + 1 , 1);
+ mvwin(inputWindow, height - 1, 1);
+
+ // Resize the windows:
+ wresize(gameWindow, (height - 2) / 2, width - 2);
+ wresize(chatWindow, ((height - 4) / 2) - (1 - (height % 2)), width - 2);
+ wresize(inputWindow, 1, width - 2);
+
+ // Refresh every window:
+ wrefresh(stdscr);
+ wrefresh(gameWindow);
+ wrefresh(chatWindow);
+ wrefresh(inputWindow);
+}
+
+// ========================================================
+// | End of client-drawing.c, copyright notice follows. |
+// ========================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/client/client-drawing.h b/source/client/client-drawing.h
new file mode 100644
index 0000000..dd7a4d4
--- /dev/null
+++ b/source/client/client-drawing.h
@@ -0,0 +1,28 @@
+// ==========================================
+// | SilverMUD Client - client-drawing.h |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// ==========================================
+#ifndef CLIENT_DRAWING_H
+#define CLIENT_DRAWING_H
+#include
+
+void redrawClientLayout(WINDOW * gameWindow, WINDOW * chatWindow, WINDOW * inputWindow);
+
+#endif
+// ========================================================
+// | End of client-drawing.h, copyright notice follows. |
+// ========================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/client/main.c b/source/client/main.c
new file mode 100644
index 0000000..6b9821c
--- /dev/null
+++ b/source/client/main.c
@@ -0,0 +1,145 @@
+// =========================================
+// | SilverMUD Client - main.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "../config.h"
+#include "../messages.h"
+#include "client-drawing.h"
+#include "receiving-thread.h"
+
+int main (int argc, char ** argv)
+{
+ // Print a welcome message:
+ printf("SilverMUD Client - Starting Now.\n"
+ "================================\n");
+
+ // Create a socket for communicating with the server:
+ int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
+ if (serverSocket == -1)
+ {
+ printf("Socket creation failed. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // Set up the server address structure to point to the server:
+ struct sockaddr_in serverAddress;
+ serverAddress.sin_family = AF_INET;
+ serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
+ serverAddress.sin_port = htons(5000);
+
+ // Connect to the server:
+ if (connect(serverSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)) != 0)
+ {
+ printf("Failed to connect to the server. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // Set up a GnuTLS session and handshake with the server:
+ gnutls_session_t tlsSession = NULL;
+ if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0)
+ {
+ exit(EXIT_FAILURE);
+ }
+
+ gnutls_anon_client_credentials_t clientKey = NULL;
+ gnutls_anon_allocate_client_credentials(&clientKey);
+ gnutls_credentials_set(tlsSession, GNUTLS_CRD_ANON, &clientKey);
+
+ gnutls_transport_set_int(tlsSession, serverSocket);
+ gnutls_priority_set_direct(tlsSession, "PERFORMANCE:+ANON-ECDH:+ANON-DH", NULL);
+
+ gnutls_handshake_set_timeout(tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+
+ int returnValue = -1;
+ do
+ {
+ returnValue = gnutls_handshake(tlsSession);
+ }
+ while (returnValue < 0 && gnutls_error_is_fatal(returnValue) == 0);
+
+ // Initialize ncurses:
+ initscr();
+ keypad(stdscr, TRUE);
+
+ if (!has_colors())
+ {
+ endwin();
+ exit(EXIT_FAILURE);
+ }
+
+ // Enable colours:
+ start_color();
+ use_default_colors();
+ init_pair(1, COLOR_GREEN, -1);
+ init_pair(2, COLOR_YELLOW, -1);
+ init_pair(3, COLOR_RED, -1);
+ init_pair(4, COLOR_BLUE, -1);
+ init_pair(5, COLOR_CYAN, -1);
+ init_pair(6, COLOR_MAGENTA, -1);
+
+ // Variables needed for the main loop:
+ int height, width;
+ getmaxyx(stdscr, height, width);
+ struct ClientToServerMessage message;
+
+ WINDOW * chatWindow, * gameWindow, * inputWindow;
+ inputWindow = newwin(1, width - 2, height - 1, 1);
+ gameWindow = newwin((height / 2) - 1, width - 2, 1, 1);
+ chatWindow = newwin((height / 2) - 3, width - 2, (height / 2) + 1, 1);
+
+ scrollok(gameWindow, TRUE);
+ scrollok(chatWindow, TRUE);
+ scrollok(inputWindow, TRUE);
+
+ redrawClientLayout(gameWindow, chatWindow, inputWindow);
+
+ struct ReceivingThreadArguments receivingThreadArguments;
+ receivingThreadArguments.chatWindow = chatWindow;
+ receivingThreadArguments.gameWindow = gameWindow;
+ receivingThreadArguments.inputWindow = inputWindow;
+ receivingThreadArguments.session = tlsSession;
+
+ pthread_t receivingThread;
+ pthread_create(&receivingThread, NULL, receivingThreadHandler, &receivingThreadArguments);
+
+ while (true)
+ {
+ wgetnstr(inputWindow, message.content, 1024);
+
+ if (message.content[0] != '\0')
+ {
+ gnutls_record_send(tlsSession, &message, 1024);
+ }
+ }
+
+ // Return a successful status code to the operating system:
+ return 0;
+}
+
+// ============================================
+// | End of main.c, copyright notice follows. |
+// ============================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/client/receiving-thread.c b/source/client/receiving-thread.c
new file mode 100644
index 0000000..41b78c3
--- /dev/null
+++ b/source/client/receiving-thread.c
@@ -0,0 +1,87 @@
+// ==========================================
+// | SilverMUD Client - receiving-thread.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// ==========================================
+#include
+#include
+
+#include "../messages.h"
+#include "client-drawing.h"
+#include "receiving-thread.h"
+
+void * receivingThreadHandler(void * threadArguments)
+{
+ start_color();
+ use_default_colors();
+ init_pair(1, COLOR_GREEN, -1);
+ init_pair(2, COLOR_YELLOW, -1);
+ init_pair(3, COLOR_RED, -1);
+ init_pair(4, COLOR_BLUE, -1);
+ init_pair(5, COLOR_CYAN, -1);
+ init_pair(6, COLOR_MAGENTA, -1);
+
+ // Unpack the thread's arguments:
+ gnutls_session_t session = ((struct ReceivingThreadArguments *)threadArguments)->session;
+ WINDOW * chatWindow = ((struct ReceivingThreadArguments *)threadArguments)->chatWindow,
+ * gameWindow = ((struct ReceivingThreadArguments *)threadArguments)->gameWindow,
+ * inputWindow = ((struct ReceivingThreadArguments *)threadArguments)->inputWindow;
+
+ // Print a message into the game window:
+ wprintw(gameWindow, "Connection successful. Welcome to ");
+ wattrset(gameWindow, COLOR_PAIR(2));
+ wprintw(gameWindow, "SilverMUD!\n");
+ wattrset(gameWindow, A_NORMAL);
+
+ struct ServerToClientMessage currentMessage;
+ while (true)
+ {
+ gnutls_record_recv(session, ¤tMessage, sizeof(struct ServerToClientMessage));
+
+ switch (currentMessage.type)
+ {
+ case SYSTEM:
+ {
+ wattrset(gameWindow, A_BOLD);
+ wprintw(gameWindow, "%s\n", currentMessage.content);
+ wattrset(gameWindow, A_NORMAL);
+ break;
+ }
+ case LOCAL_CHAT:
+ {
+ wattrset(chatWindow, A_BOLD);
+ wprintw(chatWindow, "<%s>: ", currentMessage.name);
+ wattrset(chatWindow, A_NORMAL);
+ wprintw(chatWindow, "%s\n", currentMessage.content);
+ break;
+ }
+ default:
+ {
+ wattrset(chatWindow, A_BOLD);
+ wprintw(chatWindow, "<%s>: ", currentMessage.name);
+ wattrset(chatWindow, A_NORMAL);
+ wprintw(chatWindow, "%s\n", currentMessage.content);
+ break;
+ }
+ }
+
+ redrawClientLayout(gameWindow, chatWindow, inputWindow);
+ }
+}
+
+// ========================================================
+// | End of receiving-thread.c, copyright notice follows. |
+// ========================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/client/receiving-thread.h b/source/client/receiving-thread.h
new file mode 100644
index 0000000..69a4c77
--- /dev/null
+++ b/source/client/receiving-thread.h
@@ -0,0 +1,35 @@
+// ==========================================
+// | SilverMUD Client - receiving-thread.h |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// ==========================================
+#ifndef RECEIVING_THREAD_H
+#define RECEIVING_THREAD_H
+#include
+#include
+
+struct ReceivingThreadArguments
+{
+ WINDOW * chatWindow, * gameWindow, * inputWindow;
+ gnutls_session_t session;
+};
+
+void * receivingThreadHandler(void * threadArguments);
+
+#endif
+// ========================================================
+// | End of receiving-thread.h, copyright notice follows. |
+// ========================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/messages.c b/source/messages.c
new file mode 100644
index 0000000..134c3c5
--- /dev/null
+++ b/source/messages.c
@@ -0,0 +1,113 @@
+// =========================================
+// | SilverMUD - messages.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#include
+#include
+#include
+
+#include "messages.h"
+
+// Allocate and initialize a client to server message using the passed values:
+struct ClientToServerMessage * createClientToServerMessage(char * content)
+{
+ // Allocate the needed memory for the message:
+ struct ClientToServerMessage * newMessage = calloc(1, sizeof(struct ClientToServerMessage));
+
+ // Copy the string and terminate it:
+ strncpy(newMessage->content, content, MESSAGE_CONTENT_LENGTH - 1);
+ newMessage->content[MESSAGE_CONTENT_LENGTH - 1] = '\0';
+
+ // Return the pointer:
+ return newMessage;
+}
+
+// A Scheme wrapper for creating client to server messages:
+SCM scheme_createClientToServerMessage(SCM content)
+{
+ // Check that we have been provided the right Scheme type:
+ if (scm_string_p(content))
+ {
+ // Convert the Scheme string to a C string:
+ char * contentString = scm_to_locale_stringn(content, NULL);
+
+ // Create the message:
+ struct ClientToServerMessage * message = createClientToServerMessage(contentString);
+
+ // Free the converted string:
+ free(contentString);
+
+ // Return the pointer as a Scheme object:
+ return scm_from_pointer(message, NULL);
+ }
+ else
+ {
+ return SCM_BOOL_F;
+ }
+}
+
+// Allocate and initialize a server to client message using the passed values:
+struct ServerToClientMessage * createServerToClientMessage(uint8_t type, char * name, char * content)
+{
+ // Allocate the needed memory for the message:
+ struct ServerToClientMessage * newMessage = calloc(1, sizeof(struct ServerToClientMessage));
+
+ // Copy the type:
+ newMessage->type = type;
+
+ // Copy the strings and terminate them:
+ strncpy(newMessage->name, name, MESSAGE_NAME_LENGTH - 1);
+ newMessage->name[MESSAGE_NAME_LENGTH - 1] = '\0';
+ strncpy(newMessage->content, content, MESSAGE_CONTENT_LENGTH - 1);
+ newMessage->content[MESSAGE_CONTENT_LENGTH - 1] = '\0';
+
+ return newMessage;
+}
+
+// A Scheme wrapper for creating server to client messages:
+SCM scheme_createServerToClientMessage(SCM type, SCM name, SCM content)
+{
+ // Check that we have been provided the right Scheme type:
+ if (scm_integer_p(type) && scm_string_p(name) && scm_string_p(content))
+ {
+ // Convert the integer to a C integer:
+ uint8_t typeInteger = scm_to_uint8(type);
+
+ // Convert the Scheme strings to C strings:
+ char * nameString = scm_to_locale_stringn(name, NULL);
+ char * contentString = scm_to_locale_stringn(content, NULL);
+
+ // Create the message:
+ struct ServerToClientMessage * message =
+ createServerToClientMessage(typeInteger, nameString, contentString);
+
+ // Free the converted string:
+ free(nameString);
+ free(contentString);
+
+ // Return the pointer as a Scheme object:
+ return scm_from_pointer(message, NULL);
+ }
+ else
+ {
+ return SCM_BOOL_F;
+ }
+}
+
+// ================================================
+// | End of messages.c, copyright notice follows. |
+// ================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/messages.h b/source/messages.h
new file mode 100644
index 0000000..c5c0649
--- /dev/null
+++ b/source/messages.h
@@ -0,0 +1,58 @@
+// =========================================
+// | SilverMUD - messages.h |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#ifndef MESSAGES_H
+#define MESSAGES_H
+#include
+#define MESSAGE_NAME_LENGTH 128
+#define MESSAGE_CONTENT_LENGTH 1024
+
+enum MessageTypes
+{
+ SYSTEM,
+ CLIENT_SETTING,
+ COMMAND_OUTPUT,
+ LOCAL_CHAT,
+ PLAYER_CHAT,
+ PARTY_CHAT
+};
+
+struct ClientToServerMessage
+{
+ char content[MESSAGE_CONTENT_LENGTH];
+};
+
+struct ServerToClientMessage
+{
+ uint8_t type;
+ char name[MESSAGE_NAME_LENGTH];
+ char content[MESSAGE_CONTENT_LENGTH];
+};
+
+// Allocate and initialize a client to server message using the passed values:
+struct ClientToServerMessage * createClientToServerMessage(char * content);
+SCM scheme_createClientToServerMessage(SCM content);
+
+ // Allocate and initialize a server to client message using the passed values:
+struct ServerToClientMessage * createServerToClientMessage(uint8_t type, char * name, char * content);
+SCM scheme_createServerToClientMessage(SCM type, SCM name, SCM content);
+
+#endif
+// ================================================
+// | End of messages.h, copyright notice follows. |
+// ================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/.placeholder b/source/server/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/source/server/connections.c b/source/server/connections.c
new file mode 100644
index 0000000..a49759f
--- /dev/null
+++ b/source/server/connections.c
@@ -0,0 +1,205 @@
+// =========================================
+// | SilverMUD Server - connections.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#include
+#include
+
+#include "connections.h"
+
+static struct ClientConnectionNode * findMiddle(struct ClientConnectionNode * start, struct ClientConnectionNode * end)
+{
+ while (start != end)
+ {
+ start = start->next;
+ if(start == end)
+ {
+ return start;
+ }
+ end = end->previous;
+ }
+
+ return start;
+}
+
+struct ClientConnection * findConnectionByFileDescriptor(struct ClientConnectionList * list, int fileDescriptor)
+{
+ struct ClientConnectionNode * start = list->head, * end = list->tail, * middle = findMiddle(start, end);
+ while (start != end)
+ {
+ if (middle->connection->fileDescriptor == fileDescriptor)
+ {
+ return middle->connection;
+ }
+ else if (middle->connection->fileDescriptor > fileDescriptor)
+ {
+ end = middle->previous;
+ middle = findMiddle(start, end);
+ }
+ else
+ {
+ start = middle->next;
+ middle = findMiddle(start, end);
+ }
+ }
+ if (start->connection->fileDescriptor == fileDescriptor)
+ {
+ return start->connection;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+struct ClientConnection * findConnectionByTlsSession(struct ClientConnectionList * list, gnutls_session_t * tlsSession)
+{
+
+}
+
+int removeConnectionByFileDescriptor(struct ClientConnectionList * list, int fileDescriptor)
+{
+ struct ClientConnectionNode * start = list->head, * end = list->tail, * middle = findMiddle(start, end), * toDelete = NULL;
+
+ // Find the node that is to be deleted:
+ while (start != end && toDelete == NULL)
+ {
+ if (middle->connection->fileDescriptor == fileDescriptor)
+ {
+ toDelete = middle;
+ }
+ else if (middle->connection->fileDescriptor > fileDescriptor)
+ {
+ end = middle->previous;
+ middle = findMiddle(start, end);
+ }
+ else
+ {
+ start = middle->next;
+ middle = findMiddle(start, end);
+ }
+ }
+ if (start->connection->fileDescriptor == fileDescriptor)
+ {
+ toDelete = start;
+ }
+
+ if (toDelete == NULL)
+ {
+ return -1;
+ }
+
+ // Set the appropriate pointers on other nodes:
+ if (toDelete->previous != NULL)
+ {
+ toDelete->previous->next = toDelete->next;
+ }
+ if (toDelete->next != NULL)
+ {
+ toDelete->next->previous = toDelete->previous;
+ }
+
+ // Set the appropriate pointers on the list:
+ if (list->head == toDelete)
+ {
+ list->head = toDelete->next;
+ }
+ if (list->tail == toDelete)
+ {
+ list->tail = toDelete->previous;
+ }
+
+
+ list->clientCount--;
+
+ // Free the connection:
+ free(toDelete->connection->tlsSession);
+ free(toDelete->connection);
+ free(toDelete);
+
+ return 0;
+}
+
+struct ClientConnection * addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnutls_session_t * tlsSession)
+{
+ // Allocate memory for the structures:
+ struct ClientConnectionNode * newConnectionNode = calloc(1, sizeof(struct ClientConnectionNode));
+ newConnectionNode->connection = calloc(1, sizeof(struct ClientConnection));
+
+ // Set the appropriate data in the structure:
+ newConnectionNode->next = NULL;
+ newConnectionNode->previous = NULL;
+ newConnectionNode->connection->tlsSession = tlsSession;
+ newConnectionNode->connection->fileDescriptor = fileDescriptor;
+
+ // If it's the first node in the list:
+ if (list->head == NULL && list->tail == NULL)
+ {
+ list->head = newConnectionNode;
+ list->tail = newConnectionNode;
+
+ list->clientCount++;
+
+ return newConnectionNode->connection;
+ }
+
+ // Insert it in the appropriate place in the list:
+ else
+ {
+ struct ClientConnectionNode * currentNode = list->head;
+
+ // Seek through the list until we find the appropriate spot to insert the new connection:
+ while (currentNode->connection->fileDescriptor < fileDescriptor)
+ {
+ // If we've reached the end of the list:
+ if (currentNode->next == NULL)
+ {
+ currentNode->next = newConnectionNode;
+ newConnectionNode->previous = currentNode;
+ list->tail = newConnectionNode;
+
+ list->clientCount++;
+
+ return newConnectionNode->connection;
+ }
+ else
+ {
+ currentNode = currentNode->next;
+ }
+ }
+
+ newConnectionNode->previous = currentNode->previous;
+ newConnectionNode->next = currentNode;
+ currentNode->previous = newConnectionNode;
+
+ if (newConnectionNode->previous == NULL)
+ {
+ list->head = newConnectionNode;
+ }
+ if (newConnectionNode->next == NULL)
+ {
+ list->tail = newConnectionNode;
+ }
+ list->clientCount++;
+
+ return newConnectionNode->connection;
+ }
+}
+
+// ===================================================
+// | End of connections.c, copyright notice follows. |
+// ===================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/connections.h b/source/server/connections.h
new file mode 100644
index 0000000..4ba77c9
--- /dev/null
+++ b/source/server/connections.h
@@ -0,0 +1,56 @@
+// =========================================
+// | SilverMUD Server - connections.h |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#ifndef CONNECTIONS_H
+#define CONNECTIONS_H
+#include
+#include
+
+struct ClientConnection
+{
+ gnutls_session_t * tlsSession;
+ struct Player * player;
+ int fileDescriptor;
+};
+
+struct ClientConnectionNode
+{
+ struct ClientConnection * connection;
+ struct ClientConnectionNode * next;
+ struct ClientConnectionNode * previous;
+};
+
+struct ClientConnectionList
+{
+ size_t clientCount;
+ struct ClientConnectionNode * head;
+ struct ClientConnectionNode * tail;
+};
+
+
+//struct ClientConnection * findConnectionByPlayer(struct ClientConnectionList * list);
+struct ClientConnection * findConnectionByFileDescriptor(struct ClientConnectionList * list, int fileDescriptor);
+struct ClientConnection * findConnectionByTlsSession(struct ClientConnectionList * list, gnutls_session_t * tlsSession);
+
+int removeConnectionByFileDescriptor(struct ClientConnectionList * list, int fileDescriptor);
+struct ClientConnection * addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnutls_session_t * tlsSession);
+
+#endif
+// ===================================================
+// | End of connections.h, copyright notice follows. |
+// ===================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/main.c b/source/server/main.c
new file mode 100644
index 0000000..a7bab43
--- /dev/null
+++ b/source/server/main.c
@@ -0,0 +1,273 @@
+// =========================================
+// | SilverMUD Server - main.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "player-data.h"
+#include "connections.h"
+#include "../messages.h"
+#include "scheme-integration.h"
+
+static const int PORT = 5000;
+static const int CONCURRENT_PLAYER_COUNT = 256;
+
+int main (int argc, char ** argv)
+{
+ // Print a welcome message:
+ printf("SilverMUD Server - Starting Now.\n"
+ "================================\n");
+
+ // Initialize Scheme:
+ scm_init_guile();
+
+ // Start the REPL server on a UNIX socket:
+ scm_c_eval_string("(begin (use-modules (system repl server))"
+ "(if (file-exists? \"silvermud-repl\") (delete-file \"silvermud-repl\"))"
+ "(spawn-server (make-unix-domain-server-socket #:path \"silvermud-repl\")))");
+
+ // Create a socket to listen for connections on:
+ int masterSocket = socket(AF_INET, SOCK_STREAM, 0);
+ if (masterSocket < 0)
+ {
+ fprintf(stderr, "Failed to create socket. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // Allow reusing the address so that quick re-launching doesn't fail:
+ setsockopt(masterSocket, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
+ setsockopt(masterSocket, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
+
+ // Setup the server address struct to bind the master socket to:
+ struct sockaddr_in serverAddress;
+ memset(&serverAddress, 0, sizeof(struct sockaddr_in));
+
+ // Assign the IP address and port to the server address struct:
+ serverAddress.sin_family = AF_INET;
+ serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+ serverAddress.sin_port = htons(PORT);
+
+ // Bind the master socket to the server address:
+ if ((bind(masterSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in))) != 0)
+ {
+ fprintf(stderr, "Failed to bind socket. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // Begin listening:
+ if ((listen(masterSocket, CONCURRENT_PLAYER_COUNT)) != 0)
+ {
+ fprintf(stderr, "Failed to begin listening on the master socket. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // Create an epoll instance for managing connections, and add the master socket to it:
+ int connectedClients = epoll_create(CONCURRENT_PLAYER_COUNT);
+ if (connectedClients < 0)
+ {
+ fprintf(stderr, "Failed to create epoll instance. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // Setup the epoll events we want to watch for:
+ struct epoll_event watchedEvents;
+ watchedEvents.events = EPOLLIN;
+ watchedEvents.data.fd = masterSocket;
+ epoll_ctl(connectedClients, EPOLL_CTL_ADD, masterSocket, &watchedEvents);
+
+ int eventsCount = 0;
+ struct epoll_event events[1024];
+
+ // Setup the needed anonymous certificate for TLS:
+ gnutls_global_init();
+ gnutls_anon_server_credentials_t serverKey;
+ gnutls_anon_allocate_server_credentials(&serverKey);
+ gnutls_anon_set_server_known_dh_params(serverKey, GNUTLS_SEC_PARAM_MEDIUM);
+
+ // Create a client connection list to allow us to associate TLS sessions and sockets and players:
+ struct ClientConnectionList clientConnections;
+
+ // Create some structures needed to store global state:
+ struct PlayerList * globalPlayerList = createPlayerList();
+
+ // Start a REPL thread:
+ //pthread_t schemeREPLThread;
+ //pthread_create(&schemeREPLThread, NULL, schemeREPLHandler, NULL);
+
+ while (true)
+ {
+ do
+ {
+ eventsCount = epoll_wait(connectedClients, events, 1024, -1);
+ } while (eventsCount < 0 && errno == EINTR);
+
+ if (eventsCount == -1)
+ {
+ fprintf(stderr, "epoll_wait() failed. Aborting.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ for (int index = 0; index < eventsCount; index++)
+ {
+ // If it's the master socket, it's a new client connecting:
+ if (events[index].data.fd == masterSocket)
+ {
+ // Setup a TLS Session:
+ gnutls_session_t * tlsSession = calloc(1, sizeof(gnutls_session_t));
+ gnutls_init(tlsSession, GNUTLS_SERVER);
+ gnutls_priority_set_direct(*tlsSession, "NORMAL:+ANON-ECDH:+ANON-DH", NULL);
+ gnutls_credentials_set(*tlsSession, GNUTLS_CRD_ANON, serverKey);
+ gnutls_handshake_set_timeout(*tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+
+ // Accept the connection:
+ int newSocket = accept(masterSocket, NULL, NULL);
+ gnutls_transport_set_int(*tlsSession, newSocket);
+
+ // Perform a TLS handshake:
+ int handshakeReturnValue = 0;
+ do
+ {
+ handshakeReturnValue = gnutls_handshake(*tlsSession);
+ } while (handshakeReturnValue < 0 && gnutls_error_is_fatal(handshakeReturnValue) == 0);
+
+ // If the handshake was unsuccessful, close the connection:
+ if (handshakeReturnValue < 0)
+ {
+ printf("%d", handshakeReturnValue);
+ fflush(stdout);
+ gnutls_bye(*tlsSession, 2);
+ shutdown(newSocket, 2);
+ close(newSocket);
+ break;
+ }
+
+ // Setup the epoll events we want to watch for:
+ watchedEvents.events = EPOLLIN;
+ watchedEvents.data.fd = newSocket;
+ epoll_ctl(connectedClients, EPOLL_CTL_ADD, newSocket, &watchedEvents);
+
+ // Add the connection to the list:
+ struct ClientConnection * newConnection = addNewConnection(&clientConnections, newSocket, tlsSession);
+ newConnection->player = createNewPlayer(newConnection);
+ sprintf(newConnection->player->name, "Player %02d", globalPlayerList->count + 1);
+ addToPlayerList(newConnection->player, globalPlayerList);
+
+ // Prepare a welcome message:
+ struct ServerToClientMessage welcomeMessage;
+ welcomeMessage.type = SYSTEM;
+ sprintf(welcomeMessage.content,
+ (clientConnections.clientCount > 1) ?
+ "Welcome to the server. There are %d players connected." :
+ "Welcome to the server. There is %d player connected.",
+ clientConnections.clientCount);
+
+ // Send the welcome message:
+ gnutls_record_send(*tlsSession, &welcomeMessage, sizeof(struct ServerToClientMessage));
+
+ // Report the new connection on the server:
+ printf("New connection established. %d client(s), session ID %u.\n",
+ clientConnections.clientCount, tlsSession);
+ }
+ else
+ {
+ // Find the corresponding TLS session:
+ struct ClientConnection * connection = findConnectionByFileDescriptor(&clientConnections, events[index].data.fd);
+ if (connection != NULL)
+ {
+ // Read the data from the TLS session:
+ struct ClientToServerMessage message;
+ int returnValue = gnutls_record_recv(*connection->tlsSession, &message, sizeof(struct ClientToServerMessage));
+
+ if (returnValue == 0 || returnValue == -10)
+ {
+ epoll_ctl(connectedClients, EPOLL_CTL_DEL, connection->fileDescriptor, &watchedEvents);
+ shutdown(connection->fileDescriptor, 2);
+ close(connection->fileDescriptor);
+ removeFromPlayerList(connection->player, globalPlayerList);
+ deallocatePlayer(&connection->player);
+ removeConnectionByFileDescriptor(&clientConnections, connection->fileDescriptor);
+ continue;
+ }
+ else if (returnValue == sizeof(struct ClientToServerMessage))
+ {
+ // TODO: BEGIN ACTUAL COMMAND INTERPRETER
+ // ONLY FOR DEMO
+ if (strncmp(message.content, "NAME ", 5) == 0 && message.content[5] != '\0')
+ {
+ strncpy(connection->player->name, &message.content[5], 64);
+ continue;
+ }
+ // ONLY FOR DEMO
+
+ struct ServerToClientMessage outputMessage;
+
+ // Copy the message to the output format:
+ outputMessage.type = LOCAL_CHAT;
+
+ strncpy(outputMessage.name, connection->player->name, 64);
+ strncpy(outputMessage.content, message.content, MESSAGE_CONTENT_LENGTH);
+
+ // Echo the message into all other clients: (Temporary)
+ struct ClientConnectionNode * currentClient = clientConnections.head;
+ while (currentClient != NULL)
+ {
+ gnutls_record_send(*currentClient->connection->tlsSession, &outputMessage,
+ sizeof(struct ServerToClientMessage));
+ currentClient = currentClient->next;
+ }
+ }
+ }
+ else
+ {
+ printf("Didn't find associated TLS Session!\n");
+ fflush(stdout);
+ // Remove the file descriptor from our watched set and close it:
+ epoll_ctl(connectedClients, EPOLL_CTL_DEL, events[index].data.fd, &watchedEvents);
+ close(events[index].data.fd);
+ removeConnectionByFileDescriptor(&clientConnections, events[index].data.fd);
+ }
+ }
+ }
+ }
+
+ // Wait for all other threads to terminate:
+ //pthread_join(schemeREPLThread, NULL);
+
+ // Deallocate the global state structures:
+ deallocatePlayerList(&globalPlayerList);
+
+ // Return a successful status code to the operating system:
+ return 0;
+}
+
+// ============================================
+// | End of main.c, copyright notice follows. |
+// ============================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/player-data.c b/source/server/player-data.c
new file mode 100644
index 0000000..c3d530d
--- /dev/null
+++ b/source/server/player-data.c
@@ -0,0 +1,250 @@
+// =========================================
+// | SilverMUD Server - player-data.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#include
+#include
+#include
+#include "player-data.h"
+
+// Internal Functions:
+// ===================
+static struct PlayerListNode * findMiddle(struct PlayerListNode * start, struct PlayerListNode * end)
+{
+ while (start != end)
+ {
+ start = start->next;
+ if(start == end)
+ {
+ return start;
+ }
+ end = end->previous;
+ }
+
+ return start;
+}
+// ====================
+
+// Allocates and sets up a new player according to the world's starter character sheet:
+struct Player * createNewPlayer(struct ClientConnection * connection)
+{
+ struct Player * newPlayer = calloc(1, sizeof(struct Player));
+ newPlayer->connection = connection;
+
+ return newPlayer;
+}
+
+// Deallocates a player:
+void deallocatePlayer(struct Player ** player)
+{
+ free(*player);
+ *player = NULL;
+}
+
+struct PlayerList * createPlayerList()
+{
+ struct PlayerList * newPlayerList = calloc(1, sizeof(struct PlayerList));
+ newPlayerList->count = 0;
+ newPlayerList->head = NULL;
+ newPlayerList->tail = NULL;
+
+ return newPlayerList;
+}
+
+void deallocatePlayerList(struct PlayerList ** playerList)
+{
+ struct PlayerListNode * node = (*playerList)->head, * nextNode;
+
+ // Deallocate all nodes in the list:
+ while (node != NULL)
+ {
+ nextNode = node->next;
+ free(node);
+ node = nextNode;
+ }
+
+ // Deallocate the list itself:
+ free(*playerList);
+
+ // Set the pointer to null:
+ playerList = NULL;
+}
+
+int addToPlayerList(struct Player * player, struct PlayerList * playerList)
+{
+ // Check that the player isn't already in the list:
+ if (isInPlayerList(player, playerList))
+ {
+ return playerList->count;
+ }
+ else
+ {
+ // Create a node to add to the list:
+ struct PlayerListNode * newNode = calloc(1, sizeof(struct PlayerListNode));
+ newNode->player = player;
+ newNode->next = NULL;
+ newNode->previous = NULL;
+
+ // Find the position that the new node is to go into:
+
+ // If the list is empty:
+ if (playerList->count == 0)
+ {
+ playerList->head = newNode;
+ playerList->tail = newNode;
+ playerList->count = 1;
+ return playerList->count;
+ }
+
+ struct PlayerListNode * currentNode = playerList->head;
+ while (strncmp(player->name, currentNode->player->name, 64) < 0)
+ {
+ // If we reach the end of the list:
+ if (currentNode->next == NULL)
+ {
+ currentNode->next = newNode;
+ newNode->previous = currentNode;
+ playerList->tail = newNode;
+ playerList->count++;
+
+ return playerList->count;
+ }
+
+ else
+ {
+ currentNode = currentNode->next;
+ }
+ }
+
+ // Set the appropriate pointers in the new node:
+ newNode->previous = currentNode->previous;
+ currentNode->previous = newNode;
+ newNode->next = currentNode;
+
+ // Set the proper pointers if we're at the ends of the list:
+ if (newNode->previous == NULL)
+ {
+ playerList->head = newNode;
+ }
+ if (newNode->next == NULL)
+ {
+ playerList->tail = newNode;
+ }
+
+ playerList->count++;
+
+ return playerList->count;
+ }
+}
+
+int removeFromPlayerList(struct Player * player, struct PlayerList * playerList)
+{
+ struct PlayerListNode * currentNode = playerList->head;
+ while (currentNode != NULL)
+ {
+ if (currentNode->player == player)
+ {
+ // Adjust the proper pointers:
+ if (currentNode->previous)
+ {
+ currentNode->previous->next = currentNode->next;
+ }
+ if (currentNode->next)
+ {
+ currentNode->next->previous = currentNode->previous;
+ }
+
+ // Handle the special case of the head and tail of the list:
+ if (playerList->head == currentNode)
+ {
+ playerList->head == playerList->head->next;
+ }
+ if (playerList->tail == currentNode)
+ {
+ playerList->tail == playerList->tail->previous;
+ }
+
+ // Handle the special case of an empty list:
+ if (playerList->count - 1 == 0)
+ {
+ playerList->head = NULL;
+ playerList->tail = NULL;
+ }
+
+ // Delete the node:
+ free(currentNode);
+
+ return --(playerList->count);
+ }
+ currentNode = currentNode->next;
+ }
+}
+
+
+// Returns the Player with the given name from a PlayerList, or NULL otherwise:
+struct Player * getFromPlayerList(char * playerName, struct PlayerList * playerList)
+{
+ struct PlayerListNode * start = playerList->head, * end = playerList->tail, * middle = findMiddle(start, end);
+ int returnValue = 0;
+
+ while (start != end)
+ {
+ returnValue = strncmp(middle->player->name, playerName, 64);
+
+ if (returnValue < 0)
+ {
+ start = middle->next;
+ middle = findMiddle(start, end);
+ }
+ else if (returnValue > 0)
+ {
+ end = middle->next;
+ middle = findMiddle(start, end);
+ }
+ else if (returnValue == 0)
+ {
+ return middle->player;
+ }
+ }
+ if (strncmp(start->player->name, playerName, 64) == 0)
+ {
+ return start->player;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+// Returns true if the given Player is in the given PlayerList:
+bool isInPlayerList(struct Player * player, struct PlayerList * playerList)
+{
+ struct PlayerListNode * currentNode = playerList->head;
+ while (currentNode != NULL)
+ {
+ if (currentNode->player == player)
+ {
+ return true;
+ }
+ currentNode = currentNode->next;
+ }
+ return false;
+}
+
+// ===================================================
+// | End of player-data.c, copyright notice follows. |
+// ===================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/player-data.h b/source/server/player-data.h
new file mode 100644
index 0000000..d908e5e
--- /dev/null
+++ b/source/server/player-data.h
@@ -0,0 +1,79 @@
+// =========================================
+// | SilverMUD Server - player-data.h |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// =========================================
+#ifndef PLAYER_DATA_H
+#define PLAYER_DATA_H
+#include
+#include "connections.h"
+
+// =================================================================
+// Players - A structure for representing a single player character:
+// =================================================================
+struct Player
+{
+ struct ClientConnection * connection;
+ char name[64];
+};
+
+// Functions:
+// ==========
+
+// Allocates and sets up a new player according to the world's starter character sheet:
+struct Player * createNewPlayer(struct ClientConnection * connection);
+
+// Deallocates a player:
+void deallocatePlayer(struct Player ** player);
+
+// ========================================================================================
+// Player Lists - A structure for managing a collection of players in a doubly linked list:
+// ========================================================================================
+struct PlayerListNode
+{
+ struct Player * player;
+ struct PlayerListNode * next, * previous;
+};
+
+struct PlayerList
+{
+ size_t count;
+ struct PlayerListNode * head, * tail;
+};
+
+// Functions:
+// ==========
+
+struct PlayerList * createPlayerList();
+void deallocatePlayerList(struct PlayerList ** playerList);
+
+// Adds a Player into a PlayerList, in a sorted position by character name.
+// Returns the count of players in the list:
+int addToPlayerList(struct Player * player, struct PlayerList * playerList);
+
+// Remove a Player from a PlayerList. Returns the count of players in the list:
+int removeFromPlayerList(struct Player * player, struct PlayerList * playerList);
+
+// Returns the Player with the given name from a PlayerList, or NULL otherwise:
+struct Player * getFromPlayerList(char * playerName, struct PlayerList * playerList);
+
+// Returns true if the given Player is in the given PlayerList:
+bool isInPlayerList(struct Player * player, struct PlayerList * playerList);
+
+#endif
+// ===================================================
+// | End of player-data.h, copyright notice follows. |
+// ===================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/scheme-integration.c b/source/server/scheme-integration.c
new file mode 100644
index 0000000..56bd86d
--- /dev/null
+++ b/source/server/scheme-integration.c
@@ -0,0 +1,25 @@
+// ===========================================
+// | SilverMUD Server - scheme-integration.c |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// ===========================================
+#include
+
+SCM scheme_get_player_by_name(SCM name);
+
+// ==========================================================
+// | End of scheme-integration.c, copyright notice follows. |
+// ==========================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
diff --git a/source/server/scheme-integration.h b/source/server/scheme-integration.h
new file mode 100644
index 0000000..9992bfb
--- /dev/null
+++ b/source/server/scheme-integration.h
@@ -0,0 +1,27 @@
+// ===========================================
+// | SilverMUD Server - scheme-integration.h |
+// | Copyright (C) 2023, Barra Ó Catháin |
+// | See end of file for copyright notice. |
+// ===========================================
+#ifndef SCHEME_INTEGRATION_H
+#define SCHEME_INTEGRATION_H
+
+SCM scheme_get_player_by_name(SCM name);
+
+#endif
+// ==========================================================
+// | End of scheme-integration.h, copyright notice follows. |
+// ==========================================================
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .