Compare commits

...

3 Commits

17 changed files with 368 additions and 48 deletions

View File

@ -1,10 +1,11 @@
bin_PROGRAMS = SilverMUDServer SilverMUDClient bin_PROGRAMS = SilverMUDServer SilverMUDClient
dist_doc_DATA = README.org dist_doc_DATA = README.org
SilverMUDServer_CFLAGS = -I/usr/include/guile/3.0 -I/usr -lguile-3.0 -lgc -lpthread -ldl -lgnutls -g SilverMUDServer_CFLAGS = -lgnutls -g $(GUILE_CFLAGS) $(GUILE_LIBS)
SilverMUDClient_CFLAGS = -I/usr/include/guile/3.0 -I/usr -lguile-3.0 -lgc -lpthread -ldl -lgnutls -g -lncurses SilverMUDClient_CFLAGS = -lgnutls -g -lncurses $(GUILE_CFLAGS) $(GUILE_LIBS)
SilverMUDServer_SOURCES = \ SilverMUDServer_SOURCES = \
source/messages.c \ source/messages.c \
source/server/player-data.c \
source/server/connections.c \ source/server/connections.c \
source/server/scheme-integration.c \ source/server/scheme-integration.c \
source/server/main.c source/server/main.c

View File

@ -5,4 +5,5 @@ AC_CONFIG_HEADERS([source/config.h])
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
]) ])
PKG_CHECK_MODULES([GUILE], [guile-3.0])
AC_OUTPUT AC_OUTPUT

View File

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

View File

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

View File

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

View File

View File

@ -1,4 +1,4 @@
#+TITLE: SilverMUD Design Document #+TITLE: SilverMUD Implementation Document
This document contains information about various implementation details of This document contains information about various implementation details of
SilverMUD, as a scratchpad for decisions before implementation. SilverMUD, as a scratchpad for decisions before implementation.
@ -14,6 +14,7 @@ SilverMUD, as a scratchpad for decisions before implementation.
| 3 | Local Chat Message | | 3 | Local Chat Message |
| 4 | Player Chat Message | | 4 | Player Chat Message |
| 5 | Party Chat Message | | 5 | Party Chat Message |
| 6 | Player Emote Message |
|-------+------------------------| |-------+------------------------|
Further values remain open for possible additional message types. Further values remain open for possible additional message types.

View File

@ -8,7 +8,7 @@
#include "connections.h" #include "connections.h"
struct ClientConnectionNode * findMiddle(struct ClientConnectionNode * start, struct ClientConnectionNode * end) static struct ClientConnectionNode * findMiddle(struct ClientConnectionNode * start, struct ClientConnectionNode * end)
{ {
while (start != end) while (start != end)
{ {
@ -121,7 +121,7 @@ int removeConnectionByFileDescriptor(struct ClientConnectionList * list, int fil
return 0; return 0;
} }
int addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnutls_session_t * tlsSession) struct ClientConnection * addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnutls_session_t * tlsSession)
{ {
// Allocate memory for the structures: // Allocate memory for the structures:
struct ClientConnectionNode * newConnectionNode = calloc(1, sizeof(struct ClientConnectionNode)); struct ClientConnectionNode * newConnectionNode = calloc(1, sizeof(struct ClientConnectionNode));
@ -141,7 +141,7 @@ int addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnu
list->clientCount++; list->clientCount++;
return 0; return newConnectionNode->connection;
} }
// Insert it in the appropriate place in the list: // Insert it in the appropriate place in the list:
@ -161,7 +161,7 @@ int addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnu
list->clientCount++; list->clientCount++;
return 0; return newConnectionNode->connection;
} }
else else
{ {
@ -183,7 +183,7 @@ int addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnu
} }
list->clientCount++; list->clientCount++;
return 0; return newConnectionNode->connection;
} }
} }

View File

@ -9,9 +9,9 @@
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
struct ClientConnection struct ClientConnection
{ {
// TODO: Pointer to player struct.
gnutls_session_t * tlsSession; gnutls_session_t * tlsSession;
struct Player * player;
int fileDescriptor; int fileDescriptor;
}; };
@ -35,7 +35,7 @@ struct ClientConnection * findConnectionByFileDescriptor(struct ClientConnection
struct ClientConnection * findConnectionByTlsSession(struct ClientConnectionList * list, gnutls_session_t * tlsSession); struct ClientConnection * findConnectionByTlsSession(struct ClientConnectionList * list, gnutls_session_t * tlsSession);
int removeConnectionByFileDescriptor(struct ClientConnectionList * list, int fileDescriptor); int removeConnectionByFileDescriptor(struct ClientConnectionList * list, int fileDescriptor);
int addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnutls_session_t * tlsSession); struct ClientConnection * addNewConnection(struct ClientConnectionList * list, int fileDescriptor, gnutls_session_t * tlsSession);
#endif #endif
// =================================================== // ===================================================

View File

@ -19,6 +19,7 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include "player-data.h"
#include "connections.h" #include "connections.h"
#include "../messages.h" #include "../messages.h"
#include "scheme-integration.h" #include "scheme-integration.h"
@ -100,6 +101,9 @@ int main (int argc, char ** argv)
// Create a client connection list to allow us to associate TLS sessions and sockets and players: // Create a client connection list to allow us to associate TLS sessions and sockets and players:
struct ClientConnectionList clientConnections; struct ClientConnectionList clientConnections;
// Create some structures needed to store global state:
struct PlayerList * globalPlayerList = createPlayerList();
// Start a REPL thread: // Start a REPL thread:
//pthread_t schemeREPLThread; //pthread_t schemeREPLThread;
@ -119,7 +123,7 @@ int main (int argc, char ** argv)
} }
for (int index = 0; index < eventsCount; index++) for (int index = 0; index < eventsCount; index++)
{ {
// If it's the master socket, it's a new client connecting: // If it's the master socket, it's a new client connecting:
if (events[index].data.fd == masterSocket) if (events[index].data.fd == masterSocket)
{ {
@ -133,7 +137,7 @@ int main (int argc, char ** argv)
// Accept the connection: // Accept the connection:
int newSocket = accept(masterSocket, NULL, NULL); int newSocket = accept(masterSocket, NULL, NULL);
gnutls_transport_set_int(*tlsSession, newSocket); gnutls_transport_set_int(*tlsSession, newSocket);
// Perform a TLS handshake: // Perform a TLS handshake:
int handshakeReturnValue = 0; int handshakeReturnValue = 0;
do do
@ -141,7 +145,6 @@ int main (int argc, char ** argv)
handshakeReturnValue = gnutls_handshake(*tlsSession); handshakeReturnValue = gnutls_handshake(*tlsSession);
} while (handshakeReturnValue < 0 && gnutls_error_is_fatal(handshakeReturnValue) == 0); } while (handshakeReturnValue < 0 && gnutls_error_is_fatal(handshakeReturnValue) == 0);
// If the handshake was unsuccessful, close the connection: // If the handshake was unsuccessful, close the connection:
if (handshakeReturnValue < 0) if (handshakeReturnValue < 0)
{ {
@ -159,7 +162,11 @@ int main (int argc, char ** argv)
epoll_ctl(connectedClients, EPOLL_CTL_ADD, newSocket, &watchedEvents); epoll_ctl(connectedClients, EPOLL_CTL_ADD, newSocket, &watchedEvents);
// Add the connection to the list: // Add the connection to the list:
addNewConnection(&clientConnections, newSocket, tlsSession); struct ClientConnection * newConnection = addNewConnection(&clientConnections, newSocket, tlsSession);
newConnection->player = createNewPlayer(newConnection);
sprintf(newConnection->player->name, "Player %02d", globalPlayerList->count + 1);
addToPlayerList(newConnection->player, globalPlayerList);
// Print a message: // Print a message:
printf("New connection established. %d client(s), session ID %u.\n", printf("New connection established. %d client(s), session ID %u.\n",
@ -171,16 +178,18 @@ int main (int argc, char ** argv)
struct ClientConnection * connection = findConnectionByFileDescriptor(&clientConnections, events[index].data.fd); struct ClientConnection * connection = findConnectionByFileDescriptor(&clientConnections, events[index].data.fd);
if (connection != NULL) if (connection != NULL)
{ {
// Read the data from the TLS session:
struct ClientToServerMessage message; struct ClientToServerMessage message;
int returnValue = gnutls_record_recv(*connection->tlsSession, &message, sizeof(struct ClientToServerMessage)); int returnValue = gnutls_record_recv(*connection->tlsSession, &message, sizeof(struct ClientToServerMessage));
if (returnValue == 0 || returnValue == -10) if (returnValue == 0 || returnValue == -10)
{ {
printf("Closing session ID: %u.\n", *connection->tlsSession);
epoll_ctl(connectedClients, EPOLL_CTL_DEL, connection->fileDescriptor, &watchedEvents); epoll_ctl(connectedClients, EPOLL_CTL_DEL, connection->fileDescriptor, &watchedEvents);
gnutls_bye(*connection->tlsSession, 2);
shutdown(connection->fileDescriptor, 2); shutdown(connection->fileDescriptor, 2);
close(connection->fileDescriptor);
removeConnectionByFileDescriptor(&clientConnections, connection->fileDescriptor); removeConnectionByFileDescriptor(&clientConnections, connection->fileDescriptor);
close(connection->fileDescriptor);
//deallocatePlayer(&connection->player);
continue;
} }
else if (returnValue == sizeof(struct ClientToServerMessage)) else if (returnValue == sizeof(struct ClientToServerMessage))
{ {
@ -188,18 +197,26 @@ int main (int argc, char ** argv)
// Copy the message to the output format: // Copy the message to the output format:
outputMessage.type = LOCAL_CHAT; outputMessage.type = LOCAL_CHAT;
sprintf(outputMessage.name, "UNNAMED");
if (connection->player != NULL)
{
strncpy(outputMessage.name, connection->player->name, 64);
}
else
{
sprintf(outputMessage.name, "UNNAMED");
}
strncpy(outputMessage.content, message.content, MESSAGE_CONTENT_LENGTH); strncpy(outputMessage.content, message.content, MESSAGE_CONTENT_LENGTH);
// Echo the message into all other clients: (Temporary)
struct ClientConnectionNode * currentClient = clientConnections.head; struct ClientConnectionNode * currentClient = clientConnections.head;
while (currentClient != NULL) while (currentClient != NULL)
{ {
gnutls_record_send(*currentClient->connection->tlsSession, &outputMessage, gnutls_record_send(*currentClient->connection->tlsSession, &outputMessage,
sizeof(struct ServerToClientMessage)); sizeof(struct ServerToClientMessage));
currentClient = currentClient->next; currentClient = currentClient->next;
} }
// printf("%s\n", message.content);
// fflush(stdout);
} }
} }
else else
@ -216,7 +233,10 @@ int main (int argc, char ** argv)
} }
// Wait for all other threads to terminate: // Wait for all other threads to terminate:
//pthread_join(schemeREPLThread, NULL); //pthread_join(schemeREPLThread, NULL);
// Deallocate the global state structures:
deallocatePlayerList(&globalPlayerList);
// Return a successful status code to the operating system: // Return a successful status code to the operating system:
return 0; return 0;

206
source/server/player-data.c Normal file
View File

@ -0,0 +1,206 @@
// =========================================
// | SilverMUD Server - player-data.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#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;
}
}
// 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 <https://www.gnu.org/licenses/>.

View File

@ -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 <stdbool.h>
#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 <https://www.gnu.org/licenses/>.

View File

@ -5,23 +5,7 @@
// =========================================== // ===========================================
#include <libguile.h> #include <libguile.h>
#include "scheme-integration.h" SCM scheme_get_player_by_name(SCM name);
// The function ran by the Scheme thread which runs a text-based REPL:
void * schemeREPLHandler (void * threadParameters)
{
// Initialize Scheme:
scm_init_guile();
// Enable Readline support:
scm_c_eval_string("(begin (use-modules (ice-9 readline)) (activate-readline))");
// Start a Scheme REPL:
scm_shell(0, NULL);
// Return NULL to the calling thread:
return NULL;
}
// ========================================================== // ==========================================================
// | End of scheme-integration.c, copyright notice follows. | // | End of scheme-integration.c, copyright notice follows. |

View File

@ -6,14 +6,8 @@
#ifndef SCHEME_INTEGRATION_H #ifndef SCHEME_INTEGRATION_H
#define SCHEME_INTEGRATION_H #define SCHEME_INTEGRATION_H
struct SchemeThreadArguments SCM scheme_get_player_by_name(SCM name);
{
};
// The function ran by the Scheme thread which initializes the REPL:
void * schemeREPLHandler (void * threadParameters);
#endif #endif
// ========================================================== // ==========================================================
// | End of scheme-integration.h, copyright notice follows. | // | End of scheme-integration.h, copyright notice follows. |