Compare commits

..

1 Commits
master ... old

Author SHA1 Message Date
Barra Ó Catháin ad4a65e4cd What? 2023-10-26 17:33:57 +01:00
49 changed files with 4098 additions and 1762 deletions

120
.gitignore vendored
View File

@ -1,111 +1,15 @@
# Prerequisites
*.d
# Object files
# Object files:
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
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
# Binaries:
SilverMUDClient
config.h
config.h.in
stamp-h1
SilverMUDServer
SilverMUDClientDebug
SilverMUDServerDebug
# Profiling Artifacts:
gmon.out
# LSP whatnot:
.cache
compile_commands.json

39
Makefile Normal file
View File

@ -0,0 +1,39 @@
# Compiler and linker flags needed to link to the needed libraries:
CFLAGS = `pkg-config --cflags guile-3.0`
LDFLAGS= -lpthread -lncurses -lgnutls `pkg-config --libs guile-3.0`
# Files needed to compile the client:
clientsrc = $(wildcard src/*.c) src/client/SilverMUDClient.c
clientobj = $(clientsrc:.c=.o)
# Files needed to compile the server:
serversrc = $(wildcard src/*.c) src/server/SilverMUDServer.c
serverobj = $(serversrc:.c=.o)
# Default target: Compile the client and server with aggressive optimizations and a big stack of warnings:
all: CFLAGS += -Wall -Wextra -Ofast
all: SilverMUDClient SilverMUDServer
# Debug target: Compile the client and server with profiling, debug information, debug optimization, and the
# preprocessor flag "debug" set.
debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og -D debug
debug: SilverMUDClientDebug SilverMUDServerDebug
SilverMUDClient: $(clientobj)
cc $^ $(LDFLAGS) -o $@
SilverMUDServer: $(serverobj)
cc $^ $(LDFLAGS) -o $@
SilverMUDClientDebug: $(clientobj)
cc -pg $^ $(LDFLAGS) -o $@
SilverMUDServerDebug: $(serverobj)
cc -pg $^ $(LDFLAGS) -o $@
# Start from a clean slate:
.PHONY: clean
clean:
rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug gmon.out

View File

@ -1,17 +0,0 @@
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

View File

@ -1,9 +1,111 @@
#+TITLE: SilverMUD: The Hackable Terminal-Top Roleplaying Game!
#+AUTHOR: Barra Ó Catháin
* SilverMUD: The Hackable Terminal-Top Roleplaying Game!
SilverMUD is a setting-agnostic multiplayer roleplaying game which is played
over the internet. It gives a gamemaster the same flexibility they have to
improvise and create content as they would have at a table-top, by allowing
game content to be programmed and altered on-the-fly, using an integrated Scheme
interpreter. It can be played as a multi-user dungeon, or a "terminal-top"
roleplaying game, depending on how you decide to configure it.
#+LATEX_HEADER: \RequirePackage[left=0.3in,top=0.3in,right=0.3in,bottom=0.3in, a4paper]{geometry}
* SilverMUD: The Hackable Terminal-Top Roleplaying Game.
SilverMUD is a tool for creating engaging and communal stories, all over the
world through the internet. It's designed to give a gamemaster the same power
to improvise that they have at the table, through simple programming and
easy-to-understand structures.
* Player's Guide
** Running The Client
*** How To Connect To A Server:
#+begin_example
SilverMUDClient -i <ip-address-of-game-server>
#+end_example
To connect to a server, use the command-line option =-i=, and the IP address of
the server
#+begin_example
SilverMUDClient -i <ip-address-of-game-server> -p <port-of-game-server>
#+end_example
If the server admin is hosting the server on a port other than the
default port of 5000, you can use the =-p= option with the number of the port.
If the connection is successful, you will be placed in the server's login
area. Type =/join <player name>=, where player name is a name of your choosing,
and you will be placed in the spawn area, ready to play.
*** Other Launch Options:
** The Basic Commands
SilverMUD is played through a set of very simple commands. To use a command,
type a forward-slash (/) followed immediately by the command name. The command
can be upper or lower-case.
| Command | Arguments | Effect |
|---------+------------------------------------------+---------------------------------------------------------|
| JOIN | Character Name | Logs you into the server with the given character name. |
| MOVE | Path Name/Path Number | Moves you down the given path. |
| LOOK | None | Describes the current area. |
| STAT | None | Displays your current status and character sheet. |
| SPEC | Core Stat Name | Allows you to apply spec points to a given stat. |
| TRY | Core Stat Name/Skill Name, Object Number | Attempt to use the given stat or skill on the object. |
| TALK | Character Name | Begins a conversation with another player. |
| SHOUT | None | Messages the current area, ignoring any conversations. |
* Gamemaster's Guide
** Running the Server:
* Developer's Guide
** Build Prerequisites:
SilverMUD has the following dependencies:
- GnuTLS
- ncurses
- GNU Guile
** C Style Guide:
These rules attempt to make the program as visually clear as possible, while
some rules may be made based on my own personal tastes.
For the purposes of this guide, we will use this terminology:
- () :: These are parentheses.
- [] :: These are brackets.
- {} :: These are braces.
*** Formatting:
**** Casing:
- Variables :: Variables should be in camelCase, with the exception being
variables used to store SCM values, in which case, they should in be
snake_case.
- Types :: User defined types should be in PascalCase.
**** Control Statements:
- A space should be between the keyword and the condition. This is to make
control statements visually distinct from function calls.
- Opening braces should be on the line after the control statement, and closing
braces on the line after the last statement, on it's own. This is to make the
scope of the control statement easily identifiable.
- else and else if should always be on a new line, not the same line as an if
statement's closing brace. This is to more easily distinguish the seperate
blocks.
- Control statements should never omit braces and do single statements. This is
mostly personal preference, but I do think it makes things more clear.
*** Naming:
**** Rule 0: NEVER USE i AND j!
Never use the variable names i and j. These are easy to confuse, and often make
nested loops awful to read. Name these more descriptively.
For example:
- If you are using a variable to index an array, name the variable index.
- If you are indexing multiple arrays, name it "array name + Index".
- If you are using it to count something, call it count, or "name of the
thing you are counting + count".
**** Rule 1: No one letter variable names, unless in a mathematical function.
You should never use one letter variable names. They're needlessly obtuse and
you will not remember their meaning upon re-reading of the source code. The
exception to this is when you are writing a function which replicates a
mathematical formula or function with commonly accepted notation. However, you
should consider if it would be better to break mathematical convention for
clarity inside the program, such as when the variable names are the first letter
of a word or the mathematical notation uses many similar looking variables.
**** Rule 2: Prefer to use full words in variable and function names:
You should always prefer to use full words in variable and function names. It
makes the source code much easier to read, like a sentence. Ideally, if you want
to shorten the name, use synonyms or rephrasing before you resort to removing
letters.
*** Comments:

View File

@ -1,9 +0,0 @@
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

View File

View File

@ -1,9 +0,0 @@
#+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

@ -1,15 +0,0 @@
#+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

@ -1,10 +0,0 @@
#+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,115 +0,0 @@
#+TITLE: SilverMUD Design Document
* What Is The Main Goal Of SilverMUD?
The main goal of SilverMUD is to create a text-based role playing game system
that is setting-agnostic, where a game master can create new content through
interacting with a Scheme REPL. SilverMUD will tend to favor simple abstractions
for game concepts, in order to accomplish being more setting-agnostic and easy
to play over the internet.
* Concepts:
** Worlds
Worlds are a collection of regions and dungeons, tied to a [[*Settings][setting]].
*** Regions
Regions are a collection of areas bundled together. This lets them be unloaded
and loaded as a group, as well as providing a mechanism for dividing your areas
based on locales or otherwise.
**** Areas
Areas are actual locations a player can be in. They have a description, name,
and can hold [[*Players][players]] and [[*Objects][objects]].
***** Exits
Exits are connections to other [[*Areas][areas]] or [[*Rooms][rooms]], which could be in another [[*Region][region]]
or [[*Dungeons][dungeon]]. They may have a "challenge" to pass, in order to use them, or a
requirement to access them in the first place.
*** Dungeons
Dungeons are similar to a region, but differ in one crucial way; they are
instanced, allowing for a single player or party (or potentially multiple) to
enter a copy of the dungeon, allowing for setups similar to a MUD or MMO, or
easily reusable encounters.
**** Rooms
Rooms are the dungeon equivalent to areas. They are separated purely to
strengthen the distinction between dungeons and regions; this may change to add
additional functionality
***** Exits
Exits are connections to other [[*Areas][areas]] or [[*Rooms][rooms]], which could be in another [[*Region][region]]
or [[*Dungeons][dungeon]]. They may have a "challenge" to pass, in order to use them, or a
requirement to access them in the first place. The exit leading to or from a
[[*Dungeon][dungeon]] may create and destroy the instance of the dungeon.
** Objects
Objects are a representation of items that can be in areas, rooms, or player's
inventories. They have a name, description, and uses. Looking at an object
displays the name, description, and uses.
*** Uses
Uses are Scheme functions pushed onto the event queue when a player activates
them through the use command.
Uses may additionally have a usage requirement, and a visibility requirement.
A usage requirement is a Scheme function that is passed a reference to the
player attempting to activate the use. It returns #t or #f, corresponding to the
activation being successful or a failure.
A visibility requirement is a Scheme function that is passed a reference to the
player viewing the object. It returns #t or #f, corresponding to the use being
displayed to the player or hidden.
*** Properties
Objects may have additional properties that dictate how they can be interacted
with. Currently planned properties are:
- player-pick-up :: Defines whether a object can be moved to a player's inventory
from an area or room.
- player-put-down :: Defines whether a object can be moved from a player's inventory
to an area or room.
** Events
SilverMUD centers around a central events queue; a queue of Scheme programs,
which are spawned in response to most things which happen in the game and mutate
the global state. Events are constantly evaluated by a thread which has access
to the relevant data structures.
Player commands are parsed from the command format into events by the input
thread.
** Players
Players will be designed more fully as gameplay aspects are implemented. They
currently are planned to have core statistics, skills (setting-specific
statistics), abilities, health points, ability points, and character points,
which are used to purchase core statistic points, skill points, and abilities.
** Abilities
Abilities are similar to [[*Uses][uses]], with the distinction of being tied to [[*Players][players]] and
not [[*Objects][objects]]. They have a name, description, usage cost, skill point cost, and
effect, and a usage requirement.
*** Usage Cost
The usage cost of an ability refers to the amount of ability energy it costs a
player to attempt to activate it using the /activate command.
*** Character Point Cost
The amount of character points it costs to "purchase" the skill, and add it to
the player's character.
*** Effect
Effects are Scheme functions pushed onto the event queue when a player activates
them through the /activate command.
*** Usage Requirement
Abilities may optionally have a usage requirement. A usage requirement is a
Scheme function that is passed a reference to the player attempting to activate
the ability. It returns #t or #f, corresponding to the activation being
successful or a failure.
** Settings
Settings are a combination of definitions of items, custom rules, and other
miscellaneous things like common description strings and enemy types. It's
somewhat analogous to a sourcebook or rulebook for a traditional table-top RPG,
allowing the game to take on different forms and settings depending on which
ones are being used. These are loaded as Scheme files in a [[*Worlds][world.]]

View File

@ -1,28 +0,0 @@
#+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.

View File

@ -0,0 +1,9 @@
(define (create-two-way-path from-path-description to-path-description from-area to-area arealist)
(begin
(create-path from-path-description from-area to-area arealist)
(create-path to-path-description to-area from-area arealist)))
;; Send a quick message to everyone in the game as the server:
(define (shout message)
"Send a quick message to everyone in the game as the server."
(message-everyone "SERVER" message output-queue))

View File

@ -1,61 +0,0 @@
// ==========================================
// | 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 <https://www.gnu.org/licenses/>.

View File

@ -1,28 +0,0 @@
// ==========================================
// | 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 <ncurses.h>
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 <https://www.gnu.org/licenses/>.

View File

@ -1,145 +0,0 @@
// =========================================
// | SilverMUD Client - main.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <pthread.h>
#include <ncurses.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <gnutls/gnutls.h>
#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 <https://www.gnu.org/licenses/>.

View File

@ -1,87 +0,0 @@
// ==========================================
// | SilverMUD Client - receiving-thread.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// ==========================================
#include <ncurses.h>
#include <gnutls/gnutls.h>
#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, &currentMessage, 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 <https://www.gnu.org/licenses/>.

View File

@ -1,35 +0,0 @@
// ==========================================
// | 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 <ncurses.h>
#include <gnutls/gnutls.h>
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 <https://www.gnu.org/licenses/>.

View File

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

View File

@ -1,58 +0,0 @@
// =========================================
// | SilverMUD - messages.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef MESSAGES_H
#define MESSAGES_H
#include <libguile.h>
#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 <https://www.gnu.org/licenses/>.

View File

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

View File

@ -1,56 +0,0 @@
// =========================================
// | SilverMUD Server - connections.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef CONNECTIONS_H
#define CONNECTIONS_H
#include <stddef.h>
#include <gnutls/gnutls.h>
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 <https://www.gnu.org/licenses/>.

View File

@ -1,273 +0,0 @@
// =========================================
// | SilverMUD Server - main.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#include <errno.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <pthread.h>
#include <libguile.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <gnutls/gnutls.h>
#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 <https://www.gnu.org/licenses/>.

View File

@ -1,250 +0,0 @@
// =========================================
// | 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;
}
}
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 <https://www.gnu.org/licenses/>.

View File

@ -1,79 +0,0 @@
// =========================================
// | 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

@ -1,25 +0,0 @@
// ===========================================
// | SilverMUD Server - scheme-integration.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// ===========================================
#include <libguile.h>
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 <https://www.gnu.org/licenses/>.

View File

@ -1,27 +0,0 @@
// ===========================================
// | 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 <https://www.gnu.org/licenses/>.

72
src/areadata.c Normal file
View File

@ -0,0 +1,72 @@
// areadata.c: Implements functions for playerAreas and playerPaths in SilverMUD:
// Barra Ó Catháin, 2022.
#include <string.h>
#include "areadata.h"
#include "playerdata.h"
#include "linkedlist.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 - 36);
// Properly null-terminate the strings:
createdArea->areaName[31] = '\0';
createdArea->areaDescription[MAX - 36] = '\0';
// Create a list for the paths in the area:
createdArea->pathList = createList(PATH);
// 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)
{
// Allocate the new paths:
playerPath * fromPath = malloc(sizeof(playerPath));
playerPath * toPath = malloc(sizeof(playerPath));
// Setup the from path:
strncpy(fromPath->pathName, fromDescription, 32 - 1);
fromPath->pathName[31] = '\0';
fromPath->areaToJoin = toArea;
// Setup the to path:
strncpy(toPath->pathName, toDescription, 32 - 1);
toPath->pathName[31] = '\0';
toPath->areaToJoin = fromArea;
// Add to the lists:
addToList(fromArea->pathList, fromPath, PATH);
addToList(toArea->pathList, toPath, PATH);
return 0;
}
// Create a one-way path between two areas given two areas and a string:
int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description)
{
// Allocate the new paths:
playerPath * path = calloc(1, sizeof(playerPath));
// Setup the path:
strncpy(path->pathName, description, 32 - 1);
path->pathName[31] = '\0';
path->areaToJoin = toArea;
// Add to the list:
addToList(fromArea->pathList, path, PATH);
return 0;
}

49
src/areadata.h Normal file
View File

@ -0,0 +1,49 @@
// 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: ]=-:
// ====================
// Let the compiler know that we're going to define these types:
typedef struct playerPath playerPath;
typedef struct playerArea playerArea;
typedef struct list list;
// A path, which contains a name, and a pointer to the area which the player will travel to:
struct playerPath
{
char pathName[32];
playerArea * areaToJoin;
};
// An area, containing a list of paths exiting from the area, and a name and description of the area:
struct playerArea
{
list * pathList;
char areaName[32];
char areaDescription[MAX - 35];
};
// Create an area given a name and description, returning a pointer to the new area:
playerArea * createArea(char * nameString, char * descriptionString);
// Create a path between two areas given two areas and two strings, adding it to the both area's list of paths:
int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription);
// Create a one-way path between two areas given two areas and a string, adding it to the first area's list of paths:
int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description);
// 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,382 @@
// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.5.
// PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance.
// Barry Kane, 2021
#include <netdb.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <pthread.h>
#include <ncurses.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <gnutls/gnutls.h>
#include "../queue.h"
#include "../constants.h"
#include "../playerdata.h"
#include "../texteffects.h"
#include "../inputoutput.h"
// A struct for bundling all needed parameters for a thread so we can pass them using a void pointer:
typedef struct threadparameters
{
gnutls_session_t tlsSession;
FILE * loggingStream;
bool loggingFlag;
WINDOW * window;
int characterDelay;
char * prompt;
} threadparameters;
// Use sockaddr as a type:
typedef struct sockaddr sockaddr;
// A globally available exit boolean:
bool shouldExit = false;
// A function for managing the sending thread:
void * messageSender(void * parameters)
{
threadparameters * threadParameters = parameters;
gnutls_session_t tlsSession = threadParameters->tlsSession;
FILE * loggingStream = threadParameters->loggingStream;
bool loggingFlag = threadParameters->loggingFlag;
WINDOW * window = threadParameters->window;
char * prompt = threadParameters->prompt;
userMessage sendBuffer;
// Repeatedly get input from the user, place it in a userMessage, and send it to the server:
while (!shouldExit)
{
usleep(200000);
// Clear the window:
waddstr(window, "\n\n\n");
// Print the prompt:
waddstr(window, prompt);
if (wgetnstr(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 (loggingFlag == true)
{
fputs(sendBuffer.messageContent, loggingStream);
fputs("\n", loggingStream);
fflush(loggingStream);
}
// Send the message off to the server:
messageSend(tlsSession, &sendBuffer);
memset(&sendBuffer, 0, sizeof(char) * MAX);
}
// Rejoin the main thread:
pthread_exit(NULL);
}
// A function for managing the receiving thread:
void * messageReceiver(void * parameters)
{
threadparameters * threadParameters = parameters;
gnutls_session_t tlsSession = threadParameters->tlsSession;
FILE * loggingStream = threadParameters->loggingStream;
int characterDelay = threadParameters->characterDelay;
bool loggingFlag = threadParameters->loggingFlag;
WINDOW * window = threadParameters->window;
int returnValue = 0;
userMessage receiveBuffer;
bool serverMessage = false;
int screenWidth = getmaxx(threadParameters->window);
// Repeatedly take messages from the server and print them to the chat log window:
while (!shouldExit)
{
// Get the next message:
returnValue = messageReceive(tlsSession, &receiveBuffer);
// Check we haven't been disconnected:
if (returnValue == -10 || returnValue == 0)
{
shouldExit = true;
}
// Check if it's a server message:
else if (receiveBuffer.senderName[0] == '\0')
{
// Check if the server wants to change the prompt:
if (receiveBuffer.senderName[1] != '\0')
{
strncpy(threadParameters->prompt, &receiveBuffer.senderName[1], 63);
threadParameters->prompt[63] = '\0';
}
// Check if it's a command to disconnect:
if (receiveBuffer.messageContent[0] == '\0')
{
shouldExit = true;
pthread_exit(NULL);
}
// Fit the string to the screen:
wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth);
// If it's the first server message in a block, begin a block of server messages:
if (serverMessage == false)
{
slowPrintNcurses("\n --====<>====--", characterDelay, window, true);
serverMessage = true;
}
// Print the message:
slowPrintNcurses("\n", characterDelay, window, true);
slowPrintNcurses(receiveBuffer.messageContent, characterDelay,
window, false);
slowPrintNcurses("\n", characterDelay, window, true);
}
// It's a user message:
else
{
// Fit the string to the screen:
wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1,
screenWidth - strlen(receiveBuffer.senderName) - 2);
// If the user has requested logging, insert the message into the file:
if (loggingFlag == true)
{
fputs(receiveBuffer.senderName, loggingStream);
fputs(": ", loggingStream);
fputs(receiveBuffer.messageContent, loggingStream);
fflush(loggingStream);
}
// If we're in a block of server messages, end it:
if (serverMessage == true)
{
slowPrintNcurses("\n --====<>====-- \n", characterDelay, window, true);
serverMessage = false;
}
// Print the message:
slowPrintNcurses(receiveBuffer.senderName, characterDelay, window, true);
slowPrintNcurses(": ", characterDelay, window, true);
slowPrintNcurses(receiveBuffer.messageContent, characterDelay, window, false);
}
}
// Exit the thread if shouldExit is true:
pthread_exit(NULL);
}
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.5\n", 5000);
// Parse command-line options:
while ((currentopt = getopt(argc, argv, "i:c:g:p:d:")) != -1)
{
switch (currentopt)
{
case 'i':
{
memcpy(ipAddress, optarg, 32);
break;
}
case 'c':
{
memcpy(chatLogPath, optarg, PATH_MAX + 1);
chatLog = fopen(chatLogPath, "a+");
if (chatLog == NULL)
{
chatLogging = false;
}
else
{
chatLogging = true;
}
break;
}
case 'g':
{
memcpy(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);
}
// 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);
}
// Setup a GnuTLS session and initialize it:
gnutls_session_t tlsSession = NULL;
if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0)
{
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;
logArea->characterDelay = characterDelay;
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 up the string to hold the current "prompt" that the server has sent:
messageArea->prompt = calloc(32, sizeof(char));
strcpy(messageArea->prompt, " Login > ");
logArea->prompt = messageArea->prompt;
// 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

968
src/gamelogic.c Normal file
View File

@ -0,0 +1,968 @@
// 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 <libguile.h>
#include "queue.h"
#include "constants.h"
#include "gamelogic.h"
#include "playerdata.h"
#include "linkedlist.h"
#include "inputoutput.h"
// =======================
// -=[ Main Game Loop ]=-:
// =======================
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicHandler(void * parameters)
{
gameLogicParameters * threadParameters = parameters;
inputMessage * currentInput = NULL;
queue * commandQueue = createQueue();
scm_init_guile();
while (true)
{
// Evaluate remaining commands:
while (commandQueue->itemCount != 0)
{
evaluateNextCommand(threadParameters, commandQueue);
}
// Wait if there is nothing to do:
if (threadParameters->inputQueue->itemCount == 0)
{
pthread_cond_wait(&threadParameters->inputQueue->condition, &threadParameters->inputQueue->mutex);
}
// Check for new messages and pop them off the queue:
if (threadParameters->inputQueue->itemCount != 0)
{
while (threadParameters->inputQueue->lock == true);
threadParameters->inputQueue->lock = true;
currentInput = peekQueue(threadParameters->inputQueue)->data.inputMessage;
userInputSanatize(currentInput->content->messageContent, MAX);
// A slash as the first character means the message is a user command:
if (currentInput->content->messageContent[0] == '/')
{
queueMessagedCommand(commandQueue, currentInput);
}
else if (!(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area) &&
currentInput->content->messageContent[0] != '\n')
{
// Copy the correct name into the sender name field:
strncpy(currentInput->content->senderName, currentInput->sender->playerName, 32);
currentInput->content->senderName[31] = '\0';
if(currentInput->sender->talkingWith == NULL)
{
// Allocate an array of playerInfo to store the current players in the area for the output message:
playerInfo ** recipients = calloc(PLAYERCOUNT, sizeof(playerInfo*));
// Initialize them all to NULL:
for (int index = 0; index < PLAYERCOUNT; index++)
{
recipients[index] = NULL;
}
// Get the players in the current area and add them to our array:
int recipientIndex = 0;
for (int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++)
{
if (threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea)
{
recipients[recipientIndex] = &threadParameters->connectedPlayers[playerIndex];
recipientIndex++;
}
}
// Create the outputMessage for the queue:
outputMessage * newOutputMessage = createTargetedOutputMessage(currentInput->content, recipients, recipientIndex);
// Push the message onto the queue:
pushQueue(threadParameters->outputQueue, newOutputMessage, OUTPUT_MESSAGE);
// Free the array;
free(recipients);
}
else
{
// Allocate an array of two playerInfo to store the pointers to the players in the conversation:
playerInfo ** recipients = calloc(2, (sizeof(playerInfo*)));
// Find which player is first in the player list:
bool senderIsFirst = false;
for(int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++)
{
if(&threadParameters->connectedPlayers[playerIndex] == currentInput->sender)
{
senderIsFirst = true;
break;
}
if(&threadParameters->connectedPlayers[playerIndex] == currentInput->sender->talkingWith)
{
senderIsFirst = false;
break;
}
}
// Set the proper recipients:
recipients[0] = (senderIsFirst) ? currentInput->sender : currentInput->sender->talkingWith;
recipients[1] = (senderIsFirst) ? currentInput->sender->talkingWith : currentInput->sender;
// There's only one recipient:
int recipientIndex = 2;
// Create the outputMessage for the queue:
outputMessage * newOutputMessage = createTargetedOutputMessage(currentInput->content, recipients, recipientIndex);
// Push the message onto the queue:
pushQueue(threadParameters->outputQueue, newOutputMessage, OUTPUT_MESSAGE);
// Free the array;
free(recipients);
}
}
memset(currentInput, 0, sizeof(inputMessage));
currentInput = NULL;
threadParameters->inputQueue->lock = false;
popQueue(threadParameters->inputQueue);
}
}
pthread_exit(NULL);
}
// Evaluate the next commandEvent in a queue:
void queueMessagedCommand(queue * 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:
memcpy(newCommand->command, &messageToQueue->content->messageContent[1], 16);
memcpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2],
MAX - (strlen(newCommand->command) + 2));
// Ensure the arguments are safe to parse, without adding newlines:
userNameSanatize(newCommand->command, 16);
newCommand->command[15] = '\0';
userNameSanatize(newCommand->arguments, MAX);
newCommand->arguments[MAX - 1] = '\0';
// Lowercase the command for easier comparison:
for (char * character = newCommand->command; *character; ++character)
{
*character = tolower(*character);
}
pushQueue(queue, newCommand, COMMAND);
}
// Enqueue a command to a queue:
void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength,
playerInfo * callingPlayer)
{
// Prepare the new commandEvent:
commandEvent * newCommand = calloc(1, sizeof(commandEvent));
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);
if (argumentsLength > 0)
{
strncpy(newCommand->arguments, arguments, argumentsLength);
}
// Ensure the arguments are safe to parse, without adding newlines:
userNameSanatize(newCommand->command, 16);
pushQueue(queue, newCommand, COMMAND);
}
// Evaluate the next commandEvent in a queue:
int evaluateNextCommand(gameLogicParameters * parameters, queue * queue)
{
commandEvent * currentCommand = peekQueue(queue)->data.command;
while (queue->lock);
queue->lock = true;
if (currentCommand == NULL)
{
return -1;
}
// Switch to the relevant command based on the hash:
switch (hashCommand(currentCommand->command, strlen(currentCommand->command)))
{
// Look command: Returns the description of the current area and paths:
case 5626697:
{
char formattedString[64];
userMessage * lookMessage = calloc(1, sizeof(userMessage));
lookMessage->senderName[0] = '\0';
strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33);
strncat(lookMessage->messageContent, "\n", 2);
strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35);
// Allocate an outputMessage for the queue:
outputMessage * lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
//queueTargetedOutputMessage(parameters->outputQueue, lookMessage, &currentCommand->caller, 1);
memset(lookMessage, 0, sizeof(userMessage));
// Loop through the paths and send the appropriate amount of messages:
int charCount = 13;
strncat(lookMessage->messageContent, "You can go:", 13);
if (currentCommand->caller->currentArea->pathList->itemCount > 0)
{
for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++)
{
if ((charCount + 64) >= MAX)
{
lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
memset(lookMessage, 0, sizeof(userMessage));
charCount = 0;
}
snprintf(formattedString, 64, "\n\t%ld. %s", index + 1,
getFromList(currentCommand->caller->currentArea->pathList, index)->path->pathName);
strncat(lookMessage->messageContent, formattedString, 64);
charCount += 64;
}
// Allocate another outputMessage for the queue:
lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
}
// Clear the message:
memset(lookMessage, 0, sizeof(userMessage));
if(currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area)
{
// Show the players in the area:
charCount = 23;
strncat(lookMessage->messageContent, "These players are here:", 24);
int playerNumber = 1;
for(int index = 0; index < *(parameters->playerCount); index++)
{
if (parameters->connectedPlayers[index].currentArea == currentCommand->caller->currentArea)
{
if ((charCount + 38) >= MAX)
{
lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
memset(lookMessage, 0, sizeof(userMessage));
charCount = 0;
}
snprintf(formattedString, 45, "\n%02d. %31s", playerNumber++,
parameters->connectedPlayers[index].playerName);
strncat(lookMessage->messageContent, formattedString, 64);
charCount += 38;
}
}
// Allocate another outputMessage for the queue:
lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
}
free(lookMessage);
break;
}
// Stat command: Displays the current character's sheet.
case 5987604:
{
char * formattedString = calloc(121, sizeof(char));
userMessage * statMessage = calloc(1, sizeof(userMessage));
statMessage->senderName[0] = '\0';
// Basic status: Name, level, location.
snprintf(formattedString, 120, "%s, Level %d | %s\n", currentCommand->caller->playerName,
currentCommand->caller->stats->level, currentCommand->caller->currentArea->areaName);
strncat(statMessage->messageContent, formattedString, 120);
// Current stats: Health and WISED.
snprintf(formattedString, 120,
"Health: %d/%d\nStats:\n\tWits: %2d | Intellect: %2d | Strength: %2d | Endurance: %2d | Dexerity: %2d \n",
currentCommand->caller->stats->currentHealth, currentCommand->caller->stats->maxHealth,
currentCommand->caller->stats->wits, currentCommand->caller->stats->intellect,
currentCommand->caller->stats->strength, currentCommand->caller->stats->endurance,
currentCommand->caller->stats->dexerity);
strncat(statMessage->messageContent, formattedString, 120);
// Levelling stats: Current XP, and spec points.
if (currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0)
{
snprintf(formattedString, 120, "Current Experience: %ld | Spec Points Available: %d | Skill Points Available: %d",
currentCommand->caller->stats->experience, currentCommand->caller->stats->specPoints, currentCommand->caller->stats->skillPoints);
}
else
{
snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience);
}
strncat(statMessage->messageContent, formattedString, 120);
// Allocate an outputMessage for the queue:
outputMessage * statOutputMessage = createTargetedOutputMessage(statMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE);
memset(statMessage->messageContent, 0, sizeof(char) * MAX);
if (currentCommand->caller->skills->head != NULL)
{
size_t skillIndex = 0;
int charCount = 0;
bool addNewline = false;
playerSkill * skill;
while (skillIndex < currentCommand->caller->skills->itemCount)
{
skill = getFromList(currentCommand->caller->skills, skillIndex)->skill;
skillIndex++;
snprintf(formattedString, 120, "| %2d | %31s ", skill->skillPoints, skill->skillName);
charCount += 43;
strncat(statMessage->messageContent, formattedString, 120);
if ((charCount + 43) >= MAX)
{
// Allocate an outputMessage for the queue:
statOutputMessage = createTargetedOutputMessage(statMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE);
memset(statMessage, 0, sizeof(userMessage));
charCount = 0;
break;
}
else if (addNewline)
{
strncat(statMessage->messageContent, "|\n", 3);
charCount++;
addNewline = false;
}
else
{
addNewline = true;
}
}
// Allocate an outputMessage for the queue:
statOutputMessage = createTargetedOutputMessage(statMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE);
}
free(statMessage);
free(formattedString);
break;
}
// Spec command: Assign spec points to stats:
case 5982259:
{
userMessage * specMessage = calloc(1, sizeof(userMessage));
specMessage->senderName[0] = '\0';
char * formattedString = calloc(121, sizeof(char));
if (currentCommand->caller->stats->specPoints > 0)
{
int selectedAmount = 0;
strtok(currentCommand->arguments, " ");
selectedAmount = atoi(&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);
}
// Allocate an outputMessage for the queue:
outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, specOutputMessage, OUTPUT_MESSAGE);
// Show the new stat sheet:
queue->lock = false;
queueCommand(queue, "stat", "", 5, 0, currentCommand->caller);
queue->lock = true;
// Free the finished message:
free(specMessage);
free(formattedString);
break;
}
// Try command: Attempt to use a stat or skill on an object:
case 163143:
{
// Allocate the userMessage to send:
userMessage * tryMessage = calloc(1, (sizeof(userMessage)));
tryMessage->senderName[0] = '\0';
// Temporary message until we can implement objects, events, and challenges.
strcpy(tryMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n");
// Allocate an outputMessage for the queue:
outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, tryOutputMessage, OUTPUT_MESSAGE);
// Free the userMessage:
free(tryMessage);
break;
}
// Move command: Moves the caller to a different area given a path name or number:
case 5677603:
{
char requestedPath[32];
if (strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area)
{
memcpy(requestedPath, currentCommand->arguments, 32);
userNameSanatize(requestedPath, 32);
requestedPath[31] = '\0';
if (movePlayerToArea(currentCommand->caller, requestedPath) == 0)
{
// Call the look command after moving. It's fine to unlock, because the loop won't
// continue until the command is queued:
queue->lock = false;
queueCommand(queue, "look", "", 5, 0, currentCommand->caller);
queue->lock = true;
}
}
break;
}
// Skill command: Allows you to put skill points into skills:
case 221096235:
{
userMessage * skillMessage = calloc(1, sizeof(userMessage));
skillMessage->senderName[0] = '\0';
if ((currentCommand->caller->stats->skillPoints - 1) >= 0)
{
int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments,
strlen(currentCommand->arguments), currentCommand->caller);
switch(returnValue)
{
case -1:
{
strcpy(skillMessage->messageContent, "Not a valid skill.");
break;
}
case 0:
{
strcpy(skillMessage->messageContent, "Took ");
strcat(skillMessage->messageContent, currentCommand->arguments);
strcat(skillMessage->messageContent, ".");
currentCommand->caller->stats->skillPoints--;
break;
}
}
}
else
{
strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n");
}
// Allocate an outputMessage for the queue:
outputMessage * skillOutputMessage = createTargetedOutputMessage(skillMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, skillOutputMessage, OUTPUT_MESSAGE);
free(skillMessage);
break;
}
// Listskills commands: List all available skills on the server:
case 2395990522:
{
userMessage * listMessage = calloc(1, sizeof(userMessage));
char * formattedString = calloc(121, sizeof(char));
int charCount = 0;
size_t skillIndex = 0;
bool addNewline = false;
playerSkill * currentSkill;
while (skillIndex < parameters->globalSkillList->itemCount)
{
currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill;
snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName);
charCount += 43;
strncat(listMessage->messageContent, formattedString, 120);
if ((charCount + 46) >= MAX)
{
// Allocate an outputMessage for the queue:
outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE);
memset(listMessage, 0, sizeof(userMessage));
charCount = 0;
addNewline = false;
}
else if (addNewline)
{
strncat(listMessage->messageContent, "|\n", 3);
charCount++;
addNewline = false;
}
else
{
addNewline = true;
}
skillIndex++;
}
// Allocate an outputMessage for the queue:
outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE);
free(listMessage);
free(formattedString);
break;
}
// Shout command: Allows the player to talk to everyone in the area if they are in a conversation.
case 220952831:
{
// Allocate an array of playerInfo to store the current players in the area for the output message:
playerInfo ** recipients = calloc(PLAYERCOUNT, sizeof(playerInfo*));
// Initialize them all to NULL:
for (int index = 0; index < PLAYERCOUNT; index++)
{
recipients[index] = NULL;
}
// Get the players in the current area and add them to our array:
int recipientIndex = 0;
for (int playerIndex = 0; playerIndex < *parameters->playerCount; playerIndex++)
{
if (parameters->connectedPlayers[playerIndex].currentArea == currentCommand->caller->currentArea)
{
recipients[recipientIndex] = &parameters->connectedPlayers[playerIndex];
recipientIndex++;
}
}
// Create a userMessage to be filled with the data from the command's arguments and caller:
userMessage * shoutMessage = calloc(1, sizeof(userMessage));
// Copy in the data and terminate it:
strncpy(shoutMessage->senderName, currentCommand->caller->playerName, 32);
shoutMessage->senderName[31] = '\0';
strncpy(shoutMessage->messageContent, currentCommand->arguments, MAX);
shoutMessage->messageContent[MAX - 1] = '\0';
strncat(shoutMessage->messageContent, "\n", MAX);
// Create the outputMessage for the queue:
outputMessage * shoutOutputMessage = createTargetedOutputMessage(shoutMessage, recipients, recipientIndex);
// Push the message onto the output queue:
pushQueue(parameters->outputQueue, shoutOutputMessage, OUTPUT_MESSAGE);
// Free the array:
free(recipients);
break;
}
// Talk command: Allows the player to begin a chat session with another player:
case 6012644:
{
userMessage * talkMessage = calloc(1, sizeof(userMessage));
talkMessage->senderName[0] = '\0';
// If there's no name specified, end the current chat sessions.
if (currentCommand->arguments[0] == '\0' || currentCommand->arguments == NULL)
{
currentCommand->caller->talkingWith = NULL;
strcpy(talkMessage->messageContent, "Conversation ended.");
strncat(&talkMessage->senderName[1], " > ", 4);
}
else
{
for(int playerIndex = 0; playerIndex < *parameters->playerCount; playerIndex++)
{
if(strncmp(currentCommand->arguments, parameters->connectedPlayers[playerIndex].playerName, 31) == 0)
{
currentCommand->caller->talkingWith = &(parameters->connectedPlayers[playerIndex]);
// Fill out the message to inform the receiving user what is happening:
strncpy(talkMessage->messageContent, currentCommand->caller->playerName, 32);
strcat(talkMessage->messageContent, " is talking to you.");
playerInfo ** recipients = calloc(1, (sizeof(playerInfo*)));
recipients[0] = &(parameters->connectedPlayers[playerIndex]);
// Allocate an outputMessage for the receiving user:
outputMessage * talkReceiverMessage = createTargetedOutputMessage(talkMessage, recipients, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, talkReceiverMessage, OUTPUT_MESSAGE);
// Prep the message to the calling user.
memcpy(&talkMessage->senderName[1], currentCommand->arguments, sizeof(char) * 27);
strncat(&talkMessage->senderName[1], " > ", 4);
strcpy(talkMessage->messageContent, "Conversation begun with: ");
strcat(talkMessage->messageContent, parameters->connectedPlayers[playerIndex].playerName);
}
}
}
if(talkMessage->messageContent[0] == '\0')
{
strcpy(talkMessage->messageContent, "There is no player by that name connected.");
}
// Allocate an outputMessage for the queue:
outputMessage * talkOutputMessage = createTargetedOutputMessage(talkMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, talkOutputMessage, OUTPUT_MESSAGE);
// Free the userMessage:
free(talkMessage);
break;
}
// Exit command: Sends an "empty" exit message to disconnect a client:
case 5284234:
{
// Allocate a userMessage containing null characters as the first char in both fields:
userMessage * exitMessage = calloc(1, (sizeof(userMessage)));
exitMessage->senderName[0] = '\0';
exitMessage->messageContent[0] = '\0';
// Allocate an outputMessage for the queue:
outputMessage * exitOutputMessage = createTargetedOutputMessage(exitMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, exitOutputMessage, OUTPUT_MESSAGE);
// Free the userMessage
free(exitMessage);
break;
}
// Join command: Allows the player to join the game given a name:
// TODO: Implement login/character creation. Will be a while:
case 5525172:
{
if (currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area)
{
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 = getFromList(parameters->areaList, 1)->area;
// Allocate a userMessage containing null characters as the first char in both fields:
userMessage * joinMessage = calloc(1, (sizeof(userMessage)));
memcpy(joinMessage->senderName, "\0 > \0", 5);
strcpy(joinMessage->messageContent, "Logged in successfully.");
// Allocate an outputMessage for the queue:
outputMessage * joinOutputMessage = createTargetedOutputMessage(joinMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, joinOutputMessage, OUTPUT_MESSAGE);
// Free the userMessage
free(joinMessage);
// Call the look command after joining. It's fine to unlock, because the loop won't
// continue until the command is queued:
queue->lock = false;
queueCommand(queue, "look", "", 5, 0, currentCommand->caller);
queue->lock = true;
}
}
break;
}
}
// Remove the current command and unlock the queue:
memset(currentCommand->command, 0, sizeof(char) * 16);
memset(currentCommand->arguments, 0, sizeof(char) * MAX);
currentCommand = NULL;
queue->lock = false;
popQueue(queue);
return 0;
}
// Run a stat check for the given player, returning an outcome:
outcome statCheck(playerInfo * player, int chance, coreStat statToCheck)
{
// Calculate the chance:
if (chance > 100 || chance < 0)
{
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 = (rand() % 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 for the given player, returning an outcome:
outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList)
{
// Calculate the chance:
if (chance > 100 || chance < 0)
{
return ERROR;
}
chance = 100 - chance;
// Check if the player has the given skill:
bool playerHasSkill = false;
size_t playerIndex = 0;
while (playerIndex < player->skills->itemCount)
{
if (strncmp(skillName, getFromList(player->skills, playerIndex)->skill->skillName, skillNameLength) != 0)
{
playerHasSkill = true;
break;
}
playerIndex++;
}
// If the player doesn't have the skill, check if it's in the game and is trained:
bool trainedSkill = false;
size_t globalIndex = 0;
while (globalIndex < globalSkillList->itemCount)
{
if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) != 0)
{
trainedSkill = getFromList(globalSkillList, globalIndex)->skill->trainedSkill;
break;
}
globalIndex++;
}
// Calculate the modifier:
int modifier = 0;
if (trainedSkill)
{
modifier = -100;
}
else if (playerHasSkill)
{
modifier = getFromList(player->skills, playerIndex)->skill->skillModifier * 4;
}
// Attempt the check:
int attempt = (rand() % 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 along a path in their current area:
int movePlayerToArea(playerInfo * player, char * requestedPath)
{
// Check if a number was given first:
size_t selected = atoi(requestedPath);
if (selected != 0 && !(selected > player->currentArea->pathList->itemCount))
{
if (getFromList(player->currentArea->pathList, selected - 1)->path != NULL &&
getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL)
{
player->currentArea = getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin;
return 0;
}
else
{
return 1;
}
}
// Otherwise search for the description:
for (size_t index = 0; index < player->currentArea->pathList->itemCount; index++)
{
if (strncmp(getFromList(player->currentArea->pathList, index)->path->pathName,
requestedPath, 32) == 0)
{
player->currentArea = getFromList(player->currentArea->pathList, index)->path->areaToJoin;
return 0;
}
}
return 1;
}
// A hash function for distinguishing commands for the game logic handler:
unsigned int hashCommand(char * command, unsigned int commandLength)
{
unsigned int hash = 0;
char * currentCharacter = command;
for (unsigned int index = 0; index < commandLength && *currentCharacter != '\0'; currentCharacter++)
{
hash = 37 * hash + *currentCharacter;
}
return hash;
}

86
src/gamelogic.h Normal file
View File

@ -0,0 +1,86 @@
// gamelogic.h: Function prototypes and data-structures for dealing with game logic.
// Barry Kane, 2022.
#ifndef GAMELOGIC_H
#define GAMELOGIC_H
#include "areadata.h"
#include "constants.h"
#include "playerdata.h"
// Forward-declare some data structures to prevent cyclic dependencies:
typedef struct queue queue;
typedef struct inputMessage inputMessage;
// ========================
// -=[ Data Structures ]=-:
// ========================
// An event for storing the information needed to evaluate a command:
typedef struct commandEvent commandEvent;
typedef struct commandEvent
{
playerInfo * caller;
commandEvent * next;
char * command;
char * arguments;
} commandEvent;
// A data-structure containing the needed parameters for the main game loop:
typedef struct gameLogicParameters
{
// Players:
int * playerCount;
playerInfo * connectedPlayers;
// Queues:
queue * inputQueue;
queue * outputQueue;
// Lists:
list * areaList;
list * globalSkillList;
} gameLogicParameters;
// ========================
// -=[ Functions ]=-:
// ========================
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicHandler(void * parameters);
// Enqueue a command that has been sent as a message from a user to a queue:
void queueMessagedCommand(queue * queue, inputMessage * messageToQueue);
// Evaluate the next commandEvent in a queue:
int evaluateNextCommand(gameLogicParameters * parameters, queue * queue);
// Enqueue a command to a queue:
void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength,
playerInfo * callingPlayer);
// A hash function for distinguishing commands for the game logic handler:
unsigned int hashCommand(char * command, unsigned int commandLength);
// ============================
// -=[ Gameplay Primitives ]=-:
// ============================
// The possible outcomes of a check or challenge:
typedef enum outcome
{
CRITICAL_FAILURE,
FAILURE,
SUCCESS,
CRITICAL_SUCCESS,
ERROR
} outcome;
// Move a player along a path in their current area:
int movePlayerToArea(playerInfo * player, char * requestedPath);
// Run a stat check for the given player, returning an outcome:
outcome statCheck(playerInfo * player, int chance, coreStat statToCheck);
// Run a skill check for the given player, returning an outcome:
outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList);
#endif

173
src/inputoutput.c Normal file
View File

@ -0,0 +1,173 @@
// 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 <pthread.h>
#include <gnutls/gnutls.h>
#include "queue.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;
// Continuously attempt to send the name field until it succeeds or fatally errors:
do
{
returnValue = gnutls_record_send(receivingSession, messageToSend->senderName,
sizeof(((userMessage*)0)->senderName));
} while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED);
// Continuously attempt to send the message field until it succeeds or fatally errors:
do
{
returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent,
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;
// Continuously attempt to receive the name field until it succeeds or fatally errors:
do
{
returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName,
sizeof(((userMessage*)0)->senderName));
} while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED);
// Continuously attempt to receive the message field until it succeeds or fatally errors:
do
{
returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent,
sizeof(((userMessage*)0)->messageContent));
} while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED);
return returnValue;
}
// Allocate and initialize an outputMessage targeted to a variable amount of players:
outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount)
{
// Allocate a new output message:
outputMessage * newOutputMessage = malloc(sizeof(outputMessage));
newOutputMessage->content = malloc(sizeof(userMessage));
// Allocate an array of playerInfo for the output message recepients:
newOutputMessage->recipients = malloc(sizeof(playerInfo*) * recipientsCount);
// Copy in the appropriate data:
memcpy(newOutputMessage->recipients, recipients, sizeof(playerInfo *) * recipientsCount);
memcpy(newOutputMessage->content, messageToQueue, sizeof(userMessage));
newOutputMessage->recipientsCount = recipientsCount;
// Return a pointer to the new outputMessage:
return newOutputMessage;
}
// A function for the output thread, which sends queued messages:
void * outputThreadHandler(void * parameters)
{
outputThreadParameters * variables = parameters;
queue * outputQueue = variables->outputQueue;
gnutls_session_t * tlssessions = variables->tlssessions;
playerInfo * connectedPlayers = variables->connectedPlayers;
while (true)
{
// If there's nothing to do, put the thread to sleep:
if (outputQueue->itemCount == 0)
{
pthread_cond_wait(&outputQueue->condition, &outputQueue->mutex);
}
// Run through the output queue and send all unsent messages:
while (outputQueue->itemCount != 0)
{
// Wait until the queue unlocks:
while (outputQueue->lock);
// Lock the queue:
outputQueue->lock = true;
// Get a message off the queue:
outputMessage * message = peekQueue(outputQueue)->data.outputMessage;
// Unlock the queue:
outputQueue->lock = false;
// If the first target is set to NULL, it's intended for all connected:
if (message->recipientsCount == 0)
{
for (int index = 0; index < PLAYERCOUNT; index++)
{
messageSend(tlssessions[index], message->content);
}
}
// Otherwise, send it only to the targeted players:
else
{
int sentCount = 0;
for (int index = 0; index < PLAYERCOUNT; index++)
{
if (sentCount == message->recipientsCount)
{
break;
}
if (&connectedPlayers[index] == message->recipients[sentCount])
{
sentCount++;
messageSend(tlssessions[index], message->content);
}
}
}
// Remove the output message from the queue:
popQueue(outputQueue);
}
}
}
// Sanatize user input to ensure it's okay to process:
void userInputSanatize(char * inputString, int length)
{
for (int index = 0; index <= length; index++)
{
// If it's not a printable character, it has no business being here:
if(!isprint(inputString[index]))
{
inputString[index] = '\n';
inputString[index + 1] = '\0';
break;
}
}
// Make sure it's null-terminated:
inputString[length - 1] = '\0';
}
// Sanatize user names so they display correctly:
void userNameSanatize(char * inputString, int length)
{
for(int index = 0; index <= length; index++)
{
// If it's not a printable character, it has no business being here:
if(!isprint(inputString[index]))
{
inputString[index] = '\0';
break;
}
}
// Make sure it's null-terminated:
inputString[length - 1] = '\0';
}

73
src/inputoutput.h Normal file
View File

@ -0,0 +1,73 @@
// 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 <stdlib.h>
#include <stdbool.h>
#include <gnutls/gnutls.h>
#include "constants.h"
#include "playerdata.h"
// Forward-declare some data structures to prevent cyclic dependencies:
typedef struct queue queue;
// ========================
// -=[ Data Structures ]=-:
// ========================
// Contains a character/player name and the content of a message:
typedef struct userMessage
{
char senderName[32];
char messageContent[MAX];
} userMessage;
// Contains a message sent to the server and a pointer to the playerInfo of the connection which sent it:
typedef struct inputMessage
{
playerInfo * sender;
userMessage * content;
} inputMessage;
// Contains a message to be sent, the amount of recipients, and pointers to their playerInfo:
typedef struct outputMessage
{
int recipientsCount;
userMessage * content;
playerInfo ** recipients;
} outputMessage;
// Contains pointers to the necessary information to be shared outputThreadHandler function:
typedef struct outputThreadParameters
{
queue * outputQueue;
gnutls_session_t * tlssessions;
playerInfo * connectedPlayers;
} outputThreadParameters;
// ========================
// -=[ Functions ]=-:
// ========================
// Sends a message to a given TLS session, wraps the calls to gnutls_write:
int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend);
// Receives a message from a given TLS session, wraps the calls to gnutls_read:
int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage);
// Create a targetedOutput message to be delivered to the players pointed to in recipients:
outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount);
// A function for the output thread, which sends queued messages:
void * outputThreadHandler(void * parameters);
// Sanatize user input to ensure it's okay to process:
void userInputSanatize(char * inputString, int length);
// Sanatize user names so they display correctly:
void userNameSanatize(char * inputString, int length);
#endif

480
src/linkedlist.c Normal file
View File

@ -0,0 +1,480 @@
// linkedlist.h: Function definitions for the list type for SilverMUD.
// Barry Kane, 2022.
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include "playerdata.h"
#include "linkedlist.h"
// Deallocate a given list node, including it's data:
static inline void deallocateListNode(listNode * node, listDataType type)
{
// Delete the node:
switch (type)
{
case PLAYER:
{
deallocatePlayer(node->data.player);
break;
}
case AREA:
{
destroyList(&(node->data.area->pathList));
free(node->data.area);
free(node);
break;
}
case PATH:
{
free(node->data.path);
free(node);
break;
}
case SKILL:
{
free(node->data.skill);
free(node);
break;
}
}
}
// Allocates and instantiates a list of the specified type:
list * createList(listDataType type)
{
// Allocate and clear the memory for the list:
list * newList = calloc(sizeof(list), 1);
// Set the appropriate values in the new list:
newList->type = type;
newList->itemCount = 0;
newList->head = NULL;
newList->tail = NULL;
// Return the new list:
return newList;
}
// Deallocates a list and all of it's members:
int destroyList(list ** list)
{
// Check if the list is empty:
if ((*list)->itemCount == 0)
{
free(*list);
list = NULL;
return 0;
}
else
{
while ((*list)->itemCount > 0)
{
removeFromList((*list), (*list)->type, (*list)->itemCount - 1);
}
free(*list);
*list = NULL;
return 0;
}
}
// Returns the data at a given index in a list:
listData * getFromList(list * list, size_t listIndex)
{
// Check that we were given a valid index:
if (listIndex > (list->itemCount - 1))
{
perror("Invalid index specified.\n");
return NULL;
}
// Return the head if index is 0:
else if (listIndex == 0)
{
return &(list->head->data);
}
// Loop through the entries in the list until we get to the right one:
else
{
listNode * currentNode = list->head;
while (listIndex-- > 0)
{
currentNode = currentNode->next;
}
return &(currentNode->data);
}
}
// Returns the node at a given index in a list:
listNode * getNodeFromList(list * list, size_t listIndex)
{
// Check that we were given a valid index:
if (listIndex > (list->itemCount - 1))
{
perror("Invalid index specified.\n");
return NULL;
}
// Return the head if index is 0:
else if (listIndex == 0)
{
return list->head;
}
// Loop through the entries in the list until we get to the right one:
else
{
if ((list->itemCount / 2) < listIndex)
{
listNode * currentNode = list->tail;
while (listIndex-- > 0)
{
currentNode = currentNode->previous;
}
return currentNode;
}
else
{
listNode * currentNode = list->head;
while (listIndex-- > 0)
{
currentNode = currentNode->next;
}
return currentNode;
}
}
}
// Adds the given data to the end of a list:
listNode * addToList(list * list, void * data, listDataType type)
{
// Check the type:
if (type != list->type)
{
fprintf(stderr, "Not the correct type for this list.\n");
return NULL;
}
// If this is the first item in the list:
if (list->itemCount == 0)
{
// Allocate the new node for the list:
list->head = calloc(1, sizeof(listNode));
// Set the appropriate pointers for the list:
list->head->next = NULL;
list->head->previous = NULL;
list->tail = list->head;
// Add the data to the new node:
switch (type)
{
case PATH:
{
list->head->data.path = (playerPath *)data;
break;
}
case AREA:
{
list->head->data.area = (playerArea *)data;
break;
}
case PLAYER:
{
list->head->data.player = (playerInfo *)data;
break;
}
case SKILL:
{
list->head->data.skill = (playerSkill *)data;
break;
}
}
}
else
{
// Allocate the new node at the end of the list:
list->tail->next = calloc(1, sizeof(listNode));
// Add the data to the new node:
switch (type)
{
case PATH:
{
list->tail->next->data.path = (playerPath *)data;
break;
}
case AREA:
{
list->tail->next->data.area = (playerArea *)data;
break;
}
case PLAYER:
{
list->tail->next->data.player = (playerInfo *)data;
break;
}
case SKILL:
{
list->tail->next->data.skill = (playerSkill *)data;
break;
}
}
// Set the appropriate pointers in the new node:
list->tail->next->previous = list->tail;
// Set the list's tail to the new tail:
list->tail = list->tail->next;
}
// Increase the count of items in the list:
list->itemCount++;
// Return the new item in the list:
return list->tail;
}
// Insert the given data at a given index in the list:
listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex)
{
// Check that the types are correct:
if (list->type != type)
{
fprintf(stderr, "Types do not match.\n");
return NULL;
}
// Handle the special case of adding to the end of the list:
if (listIndex == (list->itemCount - 1))
{
return addToList(list, data, type);
}
// Handle the special case of adding to the beginning of the list:
if (listIndex == 0)
{
// Create the new node:
listNode * newNode = calloc(1, sizeof(listNode));
// Add the data to the node:
switch (type)
{
case PATH:
{
newNode->data.path = (playerPath *)data;
break;
}
case AREA:
{
newNode->data.area = (playerArea *)data;
break;
}
case PLAYER:
{
newNode->data.player = (playerInfo *)data;
break;
}
case SKILL:
{
newNode->data.skill = (playerSkill *)data;
break;
}
}
// Place it in the list:
newNode->next = list->head;
newNode->previous = NULL;
list->head->previous = newNode;
list->head = newNode;
list->itemCount++;
// Return the node:
return newNode;
}
// Check that the index is valid:
if (listIndex > (list->itemCount - 1))
{
fprintf(stderr, "Index is invalid for the list.\n");
return NULL;
}
// Get the current node at the index:
listNode * currentNode = list->head;
for(size_t index = 0; index < listIndex; index++)
{
currentNode = currentNode->next;
}
// Get the node before the current node:
listNode * previousNode = currentNode->previous;
// Create the new node:
previousNode->next = calloc(1, sizeof(listNode));
currentNode->previous = previousNode->next;
previousNode->next->next = currentNode;
previousNode->next->previous = previousNode;
// Add the data to the node:
switch (type)
{
case PATH:
{
previousNode->next->data.path = (playerPath *)data;
break;
}
case AREA:
{
previousNode->next->data.area = (playerArea *)data;
break;
}
case PLAYER:
{
previousNode->next->data.player = (playerInfo *)data;
break;
}
case SKILL:
{
previousNode->next->data.skill = (playerSkill *)data;
break;
}
}
list->itemCount++;
return previousNode->next;
}
// Delete the given data from a list:
bool deleteFromList(list * list, void * data, listDataType type)
{
size_t index = 0;
if (getIndexFromList(list, data, type, &index) == false)
{
return false;
}
else
{
removeFromList(list, type, index);
return true;
}
}
// Delete the data from a given point in a list:
int removeFromList(list * list, listDataType type, size_t listIndex)
{
// Check that we're removing the correct type:
if (list->type != type)
{
return -1;
}
// Check the list index is valid:
if (listIndex > list->itemCount - 1)
{
return -2;
}
// The first node in the list:
if (listIndex == 0)
{
// Get the current head and move the list's head on:
listNode * oldHead = list->head;
list->head = list->head->next;
// If we haven't removed the last item, set the previous pointer
// in the new head to null.
if (list->head != NULL)
{
list->head->previous = NULL;
}
// Delete the node:
deallocateListNode(oldHead, type);
// Return the new amount of items in the list:
list->itemCount--;
return list->itemCount;
}
// The last node in the list:
else if (listIndex == (list->itemCount - 1))
{
// Move the tail up by one:
list->tail = list->tail->previous;
// Deallocate the former tail:
deallocateListNode(list->tail->next, type);
// Set the appropriate pointer:
list->tail->next = NULL;
// Return the new amount of items in the list:
list->itemCount--;
return list->itemCount;
}
// A node in the middle of the list:
else
{
// Get the needed node as a pointer:
listNode * nodeToDelete = getNodeFromList(list, listIndex);
// Set the appropriate pointers for the surrounding nodes:
nodeToDelete->previous->next = nodeToDelete->next;
nodeToDelete->next->previous = nodeToDelete->previous;
// Deallocate the node:
deallocateListNode(nodeToDelete, type);
// Return the new amount of items in the list:
list->itemCount--;
return list->itemCount;
}
}
// Get the index of a given piece of data in a list:
bool getIndexFromList(list * list, void * data, listDataType type, size_t * index)
{
// Check the list types are the same:
if (list->type == type)
{
fprintf(stderr, "List types do not match.\n");
return false;
}
// Search through the list, one-by-one, comparing the list items:
for (*index = 0; *index < list->itemCount; *index += 1)
{
switch (type)
{
case AREA:
{
if (getFromList(list, *index)->area == data)
{
return true;
}
break;
}
case PLAYER:
{
if (getFromList(list, *index)->player == data)
{
return true;
}
break;
}
case PATH:
{
if (getFromList(list, *index)->path == data)
{
return true;
}
break;
}
case SKILL:
{
if (getFromList(list, *index)->skill == data)
{
return true;
}
break;
}
}
}
return false;
}

84
src/linkedlist.h Normal file
View File

@ -0,0 +1,84 @@
// linkedlist.h: Defines the linked list datatype for SilverMUD.
// Barry Kane, 2022.
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
#include "areadata.h"
// Let the compiler know there will be structs defined elsewhere:
typedef struct playerPath playerPath;
typedef struct playerArea playerArea;
typedef struct playerInfo playerInfo;
typedef struct playerSkill playerSkill;
// ========================
// -=[ Data Structures ]=-:
// ========================
// An enum of the possible data types that can be stored in a list:
typedef enum listDataType
{
PATH,
AREA,
PLAYER,
SKILL
} listDataType;
// A union containing a pointers to all data types that can be stored in a list:
typedef union listData
{
playerPath * path;
playerArea * area;
playerInfo * player;
playerSkill * skill;
} listData;
// A doubly linked node for the linked list type:
typedef struct listNode listNode;
typedef struct listNode
{
listData data;
listNode * next;
listNode * previous;
} listNode;
// A header structure for the list containing the length, head, tail, and type of the list.
typedef struct list
{
listDataType type;
size_t itemCount;
listNode * head;
listNode * tail;
} list;
// ========================
// -=[ Functions ]=-:
// ========================
// Allocates and instantiates a list of the specified type:
list * createList(listDataType type);
// Deallocates a list and all of it's members:
int destroyList(list ** list);
// Returns the data at a given index in a list:
listData * getFromList(list * list, size_t listIndex);
// Returns the node at a given index in a list:
listNode * getNodeFromList(list * list, size_t listIndex);
// Adds the given data to the end of a list:
listNode * addToList(list * list, void * data, listDataType type);
// Insert the given data at a given index in the list:
listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex);
// Delete the given data from a list:
bool deleteFromList(list * list, void * data, listDataType type);
// Delete the data from a given point in a list:
int removeFromList(list * list, listDataType type, size_t listIndex);
// Get the index of a given piece of data in a list:
bool getIndexFromList(list * list, void * data, listDataType type, size_t * index);
#endif

225
src/playerdata.c Normal file
View File

@ -0,0 +1,225 @@
// 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:
listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill)
{
if (skillNameLength >= 32)
{
fprintf(stderr, "Skill name is too long. Please shorten the name and try again.\n");
return NULL;
}
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(addToList(globalSkillList, newSkill, SKILL));
}
// Take a skill and add it to the player's skill list:
int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer)
{
// Check if the skill exists in the game:
size_t globalIndex = 0;
bool skillExists = false;
while (globalIndex < globalSkillList->itemCount)
{
if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) == 0)
{
skillExists = true;
break;
}
globalIndex++;
}
if (!skillExists)
{
fprintf(stderr, "Skill doesn't exist in skill list.\n");
return -1;
}
// Check if the player has the skill:
size_t playerIndex = 0;
bool playerHasSkill = false;
while (playerIndex < targetPlayer->skills->itemCount)
{
if (strncmp(skillName, getFromList(targetPlayer->skills, playerIndex)->skill->skillName, skillNameLength) == 0)
{
playerHasSkill = true;
break;
}
playerIndex++;
}
if (playerHasSkill)
{
getFromList(targetPlayer->skills, playerIndex)->skill->skillPoints++;
}
// Copy the skill into the player's skill list:
else
{
playerSkill * newSkill = calloc(1, sizeof(playerSkill));
strncpy(newSkill->skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, 32);
printf("%s ", newSkill->skillName);
newSkill->skillPoints = 1;
addToList(targetPlayer->skills, newSkill, SKILL);
}
return 0;
}
// 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;
}
}
}
// Deallocate a player's information including the skill lists and stats:
int deallocatePlayer(playerInfo * playerToDeallocate)
{
// Deallocate the skill list:
destroyList(&(playerToDeallocate->skills));
// Deallocate the stat block:
free(playerToDeallocate->stats);
// Deallocate the player:
free(playerToDeallocate);
return 0;
}

84
src/playerdata.h Normal file
View File

@ -0,0 +1,84 @@
// 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"
#include "linkedlist.h"
// Let the compiler know there will be structs defined elsewhere:
typedef struct playerInfo playerInfo;
typedef struct playerArea playerArea;
typedef struct playerPath playerPath;
typedef struct listNode listNode;
typedef struct list list;
// The basic information that needs to be stored for a player or creature's stats:
typedef struct statBlock
{
// Levelling:
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;
// Information about a skill, including skill levels and modifiers for the player:
typedef struct playerSkill
{
char skillName[32];
int skillPoints;
int skillModifier;
bool trainedSkill;
} playerSkill;
// Information about a single player's character:
typedef struct playerInfo
{
playerInfo * talkingWith;
playerArea * currentArea;
char playerName[32];
statBlock * stats;
list * skills;
} playerInfo;
// An enum of the main stats of the game:
typedef enum coreStat
{
WITS,
INTELLECT,
STRENGTH,
ENDURANCE,
DEXERITY,
INVALID
} coreStat;
// Create a new skill and add it to the global skill list:
listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill);
// Take a skill and add it to the player's skill list:
int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer);
int takeSkillbyID(list * globalSkillList, int skillID, playerInfo * targetPlayer);
// Take a string containing a core stat name and return the core stat:
coreStat getCoreStatFromString(char * string, int stringLength);
// Deallocate a player's information including the skill lists and stats:
int deallocatePlayer(playerInfo * playerToDeallocate);
#endif

220
src/queue.c Normal file
View File

@ -0,0 +1,220 @@
// queue.c: Implements the queue data type and associated functions for SilverMUD.
// Barry Kane, 2022
#include <pthread.h>
#include "queue.h"
// Allocates and instantiates a queue:
queue * createQueue(void)
{
// Allocate the memory for the queue:
queue * newQueue = calloc(1, sizeof(queue));
// Instantiate the variables in the data-structure:
newQueue->itemCount = 0;
newQueue->front = NULL;
newQueue->back = NULL;
// Create the threading constructs:
newQueue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
newQueue->condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
// Return the pointer to the new queue:
return newQueue;
}
// Destroys a queue and all of it's members:
void destroyQueue(queue ** queue)
{
// Pop everything off of the queue:
while ((*queue)->itemCount > 0)
{
popQueue(*queue);
}
// Deallocate the queue:
free(*queue);
// Point the queue pointer to NULL;
*queue = NULL;
}
// Returns the data at the front of the given queue:
queueNode * peekQueue(queue * queue)
{
return queue->front;
}
// Removes the current data from the front of the queue:
void popQueue(queue * queue)
{
// Check if the queue is locked, and wait:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check there is actually anything to remove:
if (queue->itemCount == 0)
{
queue->lock = false;
return;
}
// Handle the special case of being the last item in the queue:
else if (queue->itemCount == 1)
{
// Deallocate the correct data:
switch (queue->front->type)
{
case EVENT:
{
// TODO: Implement events.
}
case COMMAND:
{
free(queue->front->data.command->command);
free(queue->front->data.command->arguments);
free(queue->front->data.command);
break;
}
case INPUT_MESSAGE:
{
free(queue->front->data.inputMessage->content);
free(queue->front->data.inputMessage);
break;
}
case OUTPUT_MESSAGE:
{
free(queue->front->data.outputMessage->content);
free(queue->front->data.outputMessage);
break;
}
}
// Deallocate the node:
free(queue->front);
// Set the correct variables for the queue:
queue->front = NULL;
queue->back = NULL;
queue->itemCount = 0;
// Unlock the queue:
queue->lock = false;
return;
}
// Remove the current front of the queue:
else
{
// Deallocate the correct data:
switch (queue->front->type)
{
case EVENT:
{
// TODO: Implement events.
break;
}
case COMMAND:
{
free(queue->front->data.command->command);
free(queue->front->data.command->arguments);
free(queue->front->data.command);
break;
}
case INPUT_MESSAGE:
{
free(queue->front->data.inputMessage->content);
free(queue->front->data.inputMessage);
break;
}
case OUTPUT_MESSAGE:
{
free(queue->front->data.outputMessage->content);
free(queue->front->data.outputMessage);
break;
}
}
// Save a pointer to the current node so we don't leak it:
queueNode * nodeToDelete = queue->front;
// Advance the queue:
queue->front = queue->front->next;
queue->itemCount--;
// Deallocate the old node:
free(nodeToDelete);
// Unlock the queue:
queue->lock = false;
return;
}
}
// Adds data to the back of a queue:
void pushQueue(queue * queue, void * data, queueDataType type)
{
// Check if the queue is locked:
while (queue->lock);
// Create a new node:
queueNode * newNode = malloc(sizeof(queueNode));
newNode->type = type;
// Copy the data into the correct slot for the type:
switch (type)
{
case EVENT:
{
// TODO: Implement events.
break;
}
case COMMAND:
{
newNode->data.command = data;
break;
}
case INPUT_MESSAGE:
{
newNode->data.inputMessage = data;
break;
}
case OUTPUT_MESSAGE:
{
newNode->data.outputMessage = data;
break;
}
}
// Check if the queue is locked:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Set the correct pointers:
newNode->next = NULL;
if (queue->itemCount == 0)
{
queue->front = newNode;
queue->back = newNode;
}
else
{
queue->back->next = newNode;
queue->back = newNode;
}
// Increase the queue item count:
queue->itemCount++;
// Unlock the queue:
queue->lock = false;
// Flag that the queue was modified:
pthread_cond_broadcast(&queue->condition);
}

71
src/queue.h Normal file
View File

@ -0,0 +1,71 @@
// queue.h: Defines the queue data type and associated function prototypes for SilverMUD.
// Barry Kane, 2022
#ifndef QUEUE_H
#define QUEUE_H
#include "gamelogic.h"
#include "inputoutput.h"
// Forward-declare some data structures to prevent cyclic dependencies:
typedef struct queue queue;
// ========================
// -=[ Data Structures ]=-:
// ========================
// An enum which is used to state what type of data is being stored in a queueNode:
typedef enum queueDataType
{
EVENT,
COMMAND,
INPUT_MESSAGE,
OUTPUT_MESSAGE
} queueDataType;
// A union for storing a pointer to all the types of data a queueNode may hold:
typedef union queueData
{
outputMessage * outputMessage;
inputMessage * inputMessage;
commandEvent * command;
} queueData;
// A queue node, a singly-linked list node for our queue:
typedef struct queueNode queueNode;
typedef struct queueNode
{
queueDataType type;
queueData data;
queueNode * next;
} queueNode;
// A queue, with pointers to the head and tail of the linked list, and data for multi-threading, locking, and an item count.
typedef struct queue
{
volatile bool lock;
size_t itemCount;
queueNode * front;
queueNode * back;
pthread_mutex_t mutex;
pthread_cond_t condition;
} queue;
// ========================
// -=[ Functions ]=-:
// ========================
// Allocates and instantiates a queue:
queue * createQueue(void);
// Destroys a queue and all of it's members:
void destroyQueue(queue ** queue);
// Returns the node at the front of the given queue:
queueNode * peekQueue(queue * queue);
// Removes the current node from the front of the queue:
void popQueue(queue * queue);
// Adds data to the back of a queue:
void pushQueue(queue * queue, void * data, queueDataType type);
#endif

217
src/schemeintegration.c Normal file
View File

@ -0,0 +1,217 @@
// schemeintegration.h: Function definitions for SilverMUD's Scheme integration.
// Barra Ó Catháin, 2023.
#include <string.h>
#include <libguile.h>
#include "schemeintegration.h"
// Create a player skill and add it to a given skill list from Scheme:
SCM scheme_create_skill(SCM string, SCM skilllist)
{
size_t skillNameLength = 0;
char * skillName = scm_to_latin1_stringn(string, &skillNameLength);
createSkill(scm_to_pointer(skilllist), skillName, skillNameLength, false);
free(skillName);
return SCM_BOOL_T;
}
// Create a new area and add it to the list, given a name and area description, and an area list from Scheme.
// Returns the index of the new area:
SCM scheme_create_area(SCM area_name, SCM area_description, SCM area_list)
{
// Check if the area list exists:
list * areaList = scm_to_pointer(area_list);
if (areaList == NULL || areaList->type != AREA)
{
return SCM_BOOL_F;
}
// Turn the SCM values into C strings that we can use:
char * areaName = scm_to_locale_stringn(area_name, NULL);
char * areaDescription= scm_to_locale_stringn(area_description, NULL);
// Create and add the area to the area list:
addToList(areaList, createArea(areaName, areaDescription), AREA);
// The new index of the area will be at the end of the list. Consider returning a pointer to the area:
size_t areaIndex = areaList->itemCount - 1;
// Free the strings:
free(areaName);
free(areaDescription);
// Return the area index to Scheme for further lispy hacking:
return scm_from_size_t(areaIndex);
}
// Create a one way path between two areas from scheme:
SCM scheme_create_path(SCM path_name, SCM from_area_index, SCM to_area_index, SCM area_list)
{
// Check if the area list exists:
list * areaList = scm_to_pointer(area_list);
if (areaList == NULL || areaList->type != AREA)
{
return SCM_BOOL_F;
}
// Check if the areas exist:
playerArea * fromArea = getFromList(areaList, scm_to_size_t(from_area_index))->area;
playerArea * toArea = getFromList(areaList, scm_to_size_t(to_area_index))->area;
if (fromArea == NULL || toArea == NULL)
{
return SCM_BOOL_F;
}
// Turn the SCM value into a C string that we can use:
char * pathName = scm_to_locale_stringn(path_name, NULL);
// Create the path:
createOneWayPath(fromArea, toArea, pathName);
// Free the string:
free(pathName);
// Return true to Scheme:
return SCM_BOOL_T;
}
// Change the name of an existing area in a list, given the number of the area in the list, from Scheme:
SCM scheme_change_area_name(SCM new_name, SCM area_number, SCM area_list)
{
// Check if the area exists:
list * areaList = scm_to_pointer(area_list);
size_t areaNumber = scm_to_size_t(area_number);
if (areaList->type != AREA)
{
return SCM_BOOL_F;
}
playerArea * area = getFromList(areaList, areaNumber)->area;
if (area == NULL)
{
return SCM_BOOL_F;
}
// Create a string from the Scheme string and copy it into the area:
size_t newNameLength = 0;
char * newName = scm_to_locale_stringn(new_name, &newNameLength);
memset(area->areaName, 0, 32);
if (newNameLength > 32)
{
memcpy(area->areaName, newName, 31);
area->areaName[31] = '\0';
}
else
{
memcpy(area->areaName, newName, newNameLength);
area->areaName[31] = '\0';
}
free(newName);
return SCM_BOOL_T;
}
// Change the description of an existing area in a list, given the number of the area in the list, from Scheme:
SCM scheme_change_area_description(SCM new_description, SCM area_number, SCM area_list)
{
// Check if the area exists:
list * areaList = scm_to_pointer(area_list);
size_t areaNumber = scm_to_size_t(area_number);
if (areaList->type != AREA)
{
return SCM_BOOL_F;
}
playerArea * area = getFromList(areaList, areaNumber)->area;
if (area == NULL)
{
return SCM_BOOL_F;
}
// Create a string from the Scheme string and copy it into the area:
size_t newDescriptionLength = 0;
char * newDescription = scm_to_locale_stringn(new_description, &newDescriptionLength);
memset(area->areaDescription, 0, MAX - 35);
if (newDescriptionLength > MAX - 35)
{
memcpy(area->areaDescription, newDescription, MAX - 35);
area->areaDescription[MAX - 36] = '\0';
}
else
{
memcpy(area->areaDescription, newDescription, newDescriptionLength);
area->areaDescription[MAX - 36] = '\0';
}
free(newDescription);
return SCM_BOOL_T;
}
// Message every currently connected player from Scheme:
SCM scheme_message_everyone(SCM sender_name, SCM message_content, SCM output_queue)
{
// Allocate the memory for the needed data structures:
outputMessage * newOutputMessage = calloc(1, sizeof(userMessage));
userMessage * newMessage = calloc(1, sizeof(userMessage));
// Set some basic information for the output message, allowing it to be sent to everyone:
newOutputMessage->content = newMessage;
newOutputMessage->recipientsCount = 0;
newOutputMessage->recipients = NULL;
// Convert the Scheme strings to C strings, and ensure they're NULL terminated:
scm_to_locale_stringbuf(sender_name, newMessage->senderName, 31);
newMessage->senderName[31] = '\0';
scm_to_locale_stringbuf(message_content, newMessage->messageContent, MAX - 1);
newMessage->messageContent[MAX - 1] = '\0';
// Clean up the message contents to ensure they're safe to send and display correctly:
userNameSanatize(newMessage->senderName, 32);
userInputSanatize(newMessage->messageContent, MAX);
// Push it to the queue, where it will be handled and de-allocated:
pushQueue(scm_to_pointer(output_queue), newOutputMessage, OUTPUT_MESSAGE);
// Return a Scheme #t value:
return SCM_BOOL_T;
}
// The function ran by the Scheme thread which handles all game-master and interpreter interaction:
void * schemeHandler(void * parameters)
{
// Take in the needed values from the main thread and make it back into the struct:
SchemeThreadParameters * schemeThreadParameters = parameters;
// Initialize GNU Guile:
scm_init_guile();
// Register the various functions:
scm_c_define_gsubr("create-area", 3, 0, 0, &scheme_create_area);
scm_c_define_gsubr("create-path", 4, 0, 0, &scheme_create_path);
scm_c_define_gsubr("create-skill", 2, 0, 0, &scheme_create_skill);
scm_c_define_gsubr("message-everyone", 3, 0, 0, &scheme_message_everyone);
scm_c_define_gsubr("change-area-name", 3, 0, 0, &scheme_change_area_name);
scm_c_define_gsubr("change-area-description", 3, 0, 0, &scheme_change_area_description);
// Define the various game state pointers as Scheme objects:
scm_c_define("area-list", scm_from_pointer(schemeThreadParameters->areaList, NULL));
scm_c_define("skill-list", scm_from_pointer(schemeThreadParameters->skillList, NULL));
scm_c_define("output-queue", scm_from_pointer(schemeThreadParameters->outputQueue, NULL));
// Enable readline support:
scm_c_eval_string("(begin (use-modules (ice-9 readline)) (activate-readline))");
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\")))");
// Drop into the Scheme interpreter:
scm_shell(0, NULL);
// Return NULL. This should be unreachable.
return NULL;
}

19
src/schemeintegration.h Normal file
View File

@ -0,0 +1,19 @@
// schemeintegration.h: Data-structures and function prototypes for SilverMUD's Scheme integration.
// Barra Ó Catháin, 2023.
#ifndef SCHEMEINTEGRATION_H
#define SCHEMEINTEGRATION_H
#include "queue.h"
#include "linkedlist.h"
typedef struct list list;
typedef struct queue queue;
typedef struct SchemeThreadParameters
{
list * areaList, * skillList;
queue * inputQueue, * outputQueue;
} SchemeThreadParameters;
void * schemeHandler(void * parameters);
#endif

View File

@ -0,0 +1,378 @@
// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.5
// PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance.
// Barry Kane, 2021
#include <time.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <getopt.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 "../queue.h"
#include "../areadata.h"
#include "../gamelogic.h"
#include "../constants.h"
#include "../playerdata.h"
#include "../linkedlist.h"
#include "../texteffects.h"
#include "../inputoutput.h"
#include "../schemeintegration.h"
typedef struct sockaddr sockaddr;
void sigintHandler(int signal)
{
printf("Caught signal %d.\n", signal);
exit(EXIT_SUCCESS);
}
int main(int argc, char ** argv)
{
time_t currentTime;
unsigned delay = 800;
int socketFileDesc, connectionFileDesc, length, clientsAmount,
socketCheck, activityCheck;
fd_set connectedClients;
pthread_t gameLogicThread, outputThread, schemeThread;
int clientSockets[PLAYERCOUNT];
userMessage sendBuffer, receiveBuffer;
playerInfo connectedPlayers[PLAYERCOUNT];
char testString[32] = "Hehe.";
struct sockaddr_in serverAddress, clientAddress;
char motd[2048] = "Please login with the /join command.";
queue * inputQueue = createQueue(), * outputQueue = createQueue();
// Parse command-line options:
int currentopt = 0;
while ((currentopt = getopt(argc, argv, "d:m:")) != -1)
{
switch(currentopt)
{
case 'd':
{
delay = atoi(optarg);
break;
}
case 'm':
{
strncpy(motd, optarg, 2047);
motd[2047] = '\0';
break;
}
}
}
// Set the handler for SIGINT:
signal(2, sigintHandler);
// -==[ TEST GAME-STATE INITIALIZATION ]==-
// Initialize test areas:
list * areas = createList(AREA);
addToList(areas, createArea("Login Area", motd), AREA);
// Create the areas:
addToList(areas, createArea("Octal One - Docking Bay Alpha",
"You are standing in the main docking bay of the largest station in the Octal System. "
"The sheer size of the bay is awe-inpiring. The number of ships is endless. "
"The bay is curved along with the body of the station. A catwalk runs along the back wall of the bay. "
"Two large arches lie at each end, leading to the other bays, and in the center, a set of doors leading to the interior of the station."), AREA);
addToList(areas, createArea("Octal One - Station Access Control",
"You enter into the hallway leading to the main interior of the station."
"The attendant informs you that due to a computer error, exits cannot be proccessed at the moment,"
" so you will be unable to leave, until it is resolved. "
"He apologizes profusely for the inconvenience, and clears you for entry if you wish to continue."), AREA);
addToList(areas, createArea("Octal One - Floor Zero",
"You've never quite seen so many people in one place. A large ring of shopfronts surrounds an area filled with benches and tables. "
"There's so many buisnesses in sight that you feel you could find everything you need, and this is only one of 25 main floors, "
"not to mention the 6 outer pylons which surround the main hull of the station. Staircases lead to an upper platform allowing access to the pylons. "), AREA);
// Initialize test paths:
createPath(getFromList(areas, 1)->area, getFromList(areas, 2)->area,
"Enter the station interior.", "Return to Docking Bay Alpha.");
createOneWayPath(getFromList(areas, 2)->area, getFromList(areas, 3)->area,
"Continue to station interior. ");
list * globalSkillList = createList(SKILL);
// 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 = getFromList(areas, 0)->area;
connectedPlayers[index].stats = calloc(1, sizeof(statBlock));
connectedPlayers[index].stats->specPoints = 30;
connectedPlayers[index].stats->skillPoints = 30;
connectedPlayers[index].skills = createList(SKILL);
}
// -==[ TEST GAME-STATE INITIALIZATION END ]==-
// Give an intro: Display the Silverkin Industries logo and splash text.
slowPrint(logostring, delay);
slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.5\n", delay);
// Seed random number generator from the current time:
srand((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", delay);
}
memset(&serverAddress, 0, 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", delay);
}
// 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", delay);
}
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", delay);
// 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, &gameLogicHandler, gameLogicThreadParameters);
slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", delay);
// Prepare the output queue thread:
outputThreadParameters * outputParameters = malloc(sizeof(outputThreadParameters));
outputParameters->outputQueue = outputQueue;
outputParameters->tlssessions = tlssessions;
outputParameters->connectedPlayers = connectedPlayers;
pthread_create(&outputThread, NULL, &outputThreadHandler, outputParameters);
slowPrint("\tOutput Thread is:\t\033[32;40mGREEN.\033[0m\n", delay);
// Prepare the Scheme handler thread:
SchemeThreadParameters * schemeParameters = malloc(sizeof(SchemeThreadParameters));
schemeParameters->skillList = globalSkillList;
schemeParameters->outputQueue = outputQueue;
schemeParameters->areaList = areas;
slowPrint("\tScheme Thread is:\t\033[32;40mGREEN.\033[0m\n", delay);
slowPrint("=====\n", delay);
pthread_create(&schemeThread, NULL, &schemeHandler, schemeParameters);
while(true)
{
// Clear the set of file descriptors and 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, NULL);
// 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)
{
volatile int handshakeReturnValue = 0;
clientSockets[index] = connectionFileDesc;
gnutls_transport_set_int(tlssessions[index], clientSockets[index]);
do
{
handshakeReturnValue = gnutls_handshake(tlssessions[index]);
} while (handshakeReturnValue < 0 && gnutls_error_is_fatal(handshakeReturnValue) == 0);
// If it's good, send them the welcome message:
if(handshakeReturnValue == 0)
{
// Send a greeting message:
memcpy(sendBuffer.senderName, "\0 Login > \0", 11);
strcpy(sendBuffer.messageContent, "Welcome to the server!");
messageSend(tlssessions[index], &sendBuffer);
strcpy(receiveBuffer.messageContent, "/look");
// Allocate the memory for a new input message:
inputMessage * newMessage = malloc(sizeof(inputMessage));
newMessage->content = malloc(sizeof(userMessage));
// Copy in the correct data:
memcpy(newMessage->content, &receiveBuffer, sizeof(userMessage));
newMessage->sender = &connectedPlayers[index];
// Push the new message onto the queue:
pushQueue(inputQueue, newMessage, INPUT_MESSAGE);
break;
}
// If it's not good, close it, we don't want it:
shutdown(clientSockets[index], 2);
close(clientSockets[index]);
clientSockets[index] = 0;
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 = getFromList(areas, 0)->area;
// 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
{
// Allocate the memory for a new input message:
inputMessage * newMessage = malloc(sizeof(inputMessage));
newMessage->content = malloc(sizeof(userMessage));
// Copy in the correct data:
memcpy(newMessage->content, &receiveBuffer, sizeof(userMessage));
newMessage->sender = &connectedPlayers[index];
// Push the new message onto the queue:
pushQueue(inputQueue, newMessage, INPUT_MESSAGE);
}
}
}
}
}
pthread_cancel(gameLogicThread);
pthread_cancel(outputThread);
exit(EXIT_SUCCESS);
}

121
src/texteffects.c Normal file
View File

@ -0,0 +1,121 @@
// texteffects.c: Implementation of text effect library for SilverMUD.
// Barry Kane, 2021.
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <ncurses.h>
// A character by character print, similar to a serial terminal with lower baud rate:
void slowPrint(const char * stringToPrint, int delay)
{
int characterIndex = 0;
while (stringToPrint[characterIndex] != '\0')
{
putchar(stringToPrint[characterIndex]);
// Flush the buffer so there's no line buffering.
fflush(stdout);
usleep(delay);
characterIndex++;
}
}
// The same, altered to work with ncurses:
void slowPrintNcurses(const 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);
}
// A character by character "brute-force" print, similar to Hollywood hacking scenes:
void bruteforcePrint(const char * stringToPrint, int delay)
{
unsigned int characterIndex = 0;
while (stringToPrint[characterIndex] != '\0')
{
for(unsigned char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++)
{
putchar(stringToPrint[currentCharacter]);
fflush(stdout);
usleep(delay);
putchar(8);
fflush(stdout);
}
putchar(stringToPrint[characterIndex]);
characterIndex++;
}
}
// The same, altered to work with ncurses:
void bruteforcePrintNcurses(const 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);
}
// Word-wrap a string to a given width:
void wrapString(char * stringToWrap, int stringLength, int screenWidth)
{
int characterCount = 0;
for(int index = 0; index < stringLength; index++)
{
if (stringToWrap[index] == '\n')
{
characterCount = 0;
}
else
{
characterCount++;
}
if (characterCount == screenWidth)
{
while (!isspace(stringToWrap[index]) && index > 0)
{
index--;
}
if (index == 0)
{
return;
}
stringToWrap[index] = '\n';
index++;
characterCount = 0;
}
}
}

38
src/texteffects.h Normal file
View File

@ -0,0 +1,38 @@
// 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(const char * stringToPrint, int delay);
// The same, altered to work with ncurses:
void slowPrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded);
// A character by character "brute-force" print, similar to Hollywood hacking scenes:
void bruteforcePrint(const char * stringToPrint, int delay);
// The same, altered to work with ncurses:
void bruteforcePrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded);
// Word-wrap a string to a given width:
void wrapString(char * stringToWrap, int stringLength, int screenWidth);
// A string containing an ASCII art version of the Silverkin Industries logo.
const char * logostring =
" ///////\n"
" //////////////////////////////////////////\n"
" ///////////////////////////////////////////////////////////\n"
" ////////// ////////////////////////////\n"
" ### # # # # ##### ### # # # # # /////////////////\n"
" ## # # # # ## # # ### # ## # //////////////\n"
" ## # # # # # ### # # # # # # /////////\n"
" ### # ### # ##### # # # # # # # ///////\n"
" # ## # ##### # # ### ### ### # ##### ### //////\n"
" # # # # # # # # ## # # # # ## ## ////\n"
" # # # # # # # # ## # ### # # ## //\n"
" # # ### ##### ##### ### # # # # #### ### /\n";
#endif

View File

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

@ -0,0 +1,46 @@
#include "../src/linkedlist.h"
#include <stdio.h>
static inline void printAreaList(list * areaList)
{
listData * currentData;
for(int index = 0; index < areaList->itemCount; index++)
{
currentData = getFromList(areaList, index);
printf("%d\t| %s - %s\n", index, currentData->area->areaName, currentData->area->areaDescription);
}
}
void main()
{
list * areaList = createList(AREA);
char areaName[256];
char areaDescription[256];
printf("\n--==[ Generating a list of ten items. ]==--\n\n");
for(int count = 1; count <= 10; count++)
{
sprintf(areaName, "Area %d", count);
sprintf(areaDescription, "This is Area %d.", count);
addToList(areaList, createArea(areaName, areaDescription) , AREA);
}
printAreaList(areaList);
printf("\n--==[ Inserting items into specific indexes. ]==--\n\n");
insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 0);
insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 6);
insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 11);
printAreaList(areaList);
printf("\n--==[ Removing certain areas from the list. ]==--\n\n");
removeFromList(areaList, AREA, 12);
removeFromList(areaList, AREA, 6);
removeFromList(areaList, AREA, 0);
printAreaList(areaList);
destroyList(&areaList);
printf("");
}

132
tests/queue-test.c Normal file
View File

@ -0,0 +1,132 @@
// Test for the queue type in SilverMUD:
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "../src/queue.h"
#define formatBoolean(b) ((b) ? "true" : "false")
#define formatEquality(b) ((b) ? "equal" : "not equal")
int main(void)
{
// Create a queue:
printf("-=[ Creating queue ]=-:\n");
queue * testQueue = createQueue();
// Check that the queue has the correct values:
printf("-=[ Checking initial values ]=-:\n");
printf("- Item count should be 0:\n");
assert(testQueue->itemCount == 0);
printf("Item count is %d.\n\n", testQueue->itemCount);
printf("- Lock should be false:\n");
assert(testQueue->lock == false);
printf("Lock is %s.\n\n", formatBoolean(testQueue->lock));
printf("- Front should be (nil):\n");
assert(testQueue->front == NULL);
printf("Front is %p.\n\n", testQueue->front);
printf("- Back should be (nil):\n");
assert(testQueue->back == NULL);
printf("Back is %p.\n\n", testQueue->front);
// Create some items for the queue:
inputMessage * testInputMessage = malloc(sizeof(inputMessage));
testInputMessage->sender = NULL;
testInputMessage->next = NULL;
testInputMessage->content = malloc(sizeof(userMessage));
strcpy(testInputMessage->content->senderName,"Queue Test Input Sender");
strcpy(testInputMessage->content->messageContent, "Queue Test Input Content - Hello!");
outputMessage * testOutputMessage = malloc(sizeof(outputMessage));
for(int index = 0; index < PLAYERCOUNT; index++)
{
testOutputMessage->targets[index] = NULL;
}
testOutputMessage->next = NULL;
testOutputMessage->content = malloc(sizeof(userMessage));
strcpy(testOutputMessage->content->senderName, "Queue Test Output Sender");
strcpy(testOutputMessage->content->messageContent, "Queue Test Output Content - World!");
commandEvent * testCommandEvent = malloc(sizeof(commandEvent));
testCommandEvent->next = NULL;
testCommandEvent->caller = NULL;
testCommandEvent->command = malloc(5 * sizeof(char));
testCommandEvent->arguments = malloc(15 * sizeof(char));
strcpy(testCommandEvent->command, "Test");
strcpy(testCommandEvent->arguments, "Test Arguments");
// Add them to the queue:
printf("-=[ Adding items to the queue ]=-:\n");
printf("- First item, Item count should be 1. Front and Back should be equal.\n");
pushQueue(testQueue, testInputMessage, INPUT_MESSAGE);
assert(testQueue->itemCount == 1);
assert(testQueue->front == testQueue->back);
printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount,
formatEquality(testQueue->front == testQueue->back));
printf("- Second item, Item count should be 2. Front and Back should be not equal.\n");
pushQueue(testQueue, testOutputMessage, OUTPUT_MESSAGE);
assert(testQueue->itemCount == 2);
assert(testQueue->front != testQueue->back);
printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount,
formatEquality(testQueue->front == testQueue->back));
printf("- Third item, Item count should be 3. Front and Back should be not equal.\n");
pushQueue(testQueue, testCommandEvent, COMMAND);
assert(testQueue->itemCount == 3);
assert(testQueue->front != testQueue->back);
printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount,
formatEquality(testQueue->front == testQueue->back));
printf("-=[ Checking items and popping from queue ]=-:\n");
printf("- First item peeked should point to testInputMessage.\n");
assert(peekQueue(testQueue)->data.inputMessage == testInputMessage);
printf("Peeked data is located at: %p, testInputMessage is located at: %p.\n\n",
peekQueue(testQueue)->data.inputMessage, testInputMessage);
printf("- Popping first item, Item count should be 2, Front and Back should not be equal.\n");
popQueue(testQueue);
assert(testQueue->itemCount == 2);
assert(testQueue->front != testQueue->back);
printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount,
formatEquality(testQueue->front == testQueue->back));
printf("- Second item peeked should point to testOutputMessage.\n");
assert(peekQueue(testQueue)->data.outputMessage == testOutputMessage);
printf("Peeked data is located at: %p, testOutputMessage is located at: %p.\n\n",
peekQueue(testQueue)->data.outputMessage, testOutputMessage);
printf("- Popping second item, Item count should be 1, Front and Back should be equal.\n");
popQueue(testQueue);
assert(testQueue->itemCount == 1);
assert(testQueue->front == testQueue->back);
printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount,
formatEquality(testQueue->front == testQueue->back));
printf("- Third item peeked should point to testCommandEvent.\n");
assert(peekQueue(testQueue)->data.command == testCommandEvent);
printf("Peeked data is located at: %p, testCommandEvent is located at: %p.\n\n",
peekQueue(testQueue)->data.command, testCommandEvent);
printf("- Popping third item:\n");
popQueue(testQueue);
printf("- Item count should be 0:\n");
assert(testQueue->itemCount == 0);
printf("Item count is %d.\n\n", testQueue->itemCount);
printf("- Lock should be false:\n");
assert(testQueue->lock == false);
printf("Lock is %s.\n\n", formatBoolean(testQueue->lock));
printf("- Front should be (nil):\n");
assert(testQueue->front == NULL);
printf("Front is %p.\n\n", testQueue->front);
printf("- Back should be (nil):\n");
assert(testQueue->back == NULL);
printf("Back is %p.\n\n", testQueue->front);
}