Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Barra Ó Catháin | ad4a65e4cd |
|
@ -1,111 +1,15 @@
|
||||||
# Prerequisites
|
# Object files:
|
||||||
*.d
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
*.o
|
||||||
*.ko
|
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Linker output
|
# Binaries:
|
||||||
*.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
|
|
||||||
SilverMUDClient
|
SilverMUDClient
|
||||||
config.h
|
SilverMUDServer
|
||||||
config.h.in
|
SilverMUDClientDebug
|
||||||
stamp-h1
|
SilverMUDServerDebug
|
||||||
|
|
||||||
|
# Profiling Artifacts:
|
||||||
|
gmon.out
|
||||||
|
|
||||||
|
# LSP whatnot:
|
||||||
|
.cache
|
||||||
|
compile_commands.json
|
|
@ -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
|
||||||
|
|
17
Makefile.am
17
Makefile.am
|
@ -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
|
|
120
README.org
120
README.org
|
@ -1,9 +1,111 @@
|
||||||
#+TITLE: SilverMUD: The Hackable Terminal-Top Roleplaying Game!
|
#+LATEX_HEADER: \RequirePackage[left=0.3in,top=0.3in,right=0.3in,bottom=0.3in, a4paper]{geometry}
|
||||||
#+AUTHOR: Barra Ó Catháin
|
* SilverMUD: The Hackable Terminal-Top Roleplaying Game.
|
||||||
* SilverMUD: The Hackable Terminal-Top Roleplaying Game!
|
SilverMUD is a tool for creating engaging and communal stories, all over the
|
||||||
SilverMUD is a setting-agnostic multiplayer roleplaying game which is played
|
world through the internet. It's designed to give a gamemaster the same power
|
||||||
over the internet. It gives a gamemaster the same flexibility they have to
|
to improvise that they have at the table, through simple programming and
|
||||||
improvise and create content as they would have at a table-top, by allowing
|
easy-to-understand structures.
|
||||||
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"
|
* Player's Guide
|
||||||
roleplaying game, depending on how you decide to configure it.
|
** 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:
|
||||||
|
|
|
@ -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
|
|
|
@ -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:
|
|
|
@ -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:
|
|
|
@ -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:
|
|
||||||
|
|
|
@ -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.]]
|
|
|
@ -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.
|
|
|
@ -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))
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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, ¤tMessage, sizeof(struct ServerToClientMessage));
|
|
||||||
|
|
||||||
switch (currentMessage.type)
|
|
||||||
{
|
|
||||||
case SYSTEM:
|
|
||||||
{
|
|
||||||
wattrset(gameWindow, A_BOLD);
|
|
||||||
wprintw(gameWindow, "%s\n", currentMessage.content);
|
|
||||||
wattrset(gameWindow, A_NORMAL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOCAL_CHAT:
|
|
||||||
{
|
|
||||||
wattrset(chatWindow, A_BOLD);
|
|
||||||
wprintw(chatWindow, "<%s>: ", currentMessage.name);
|
|
||||||
wattrset(chatWindow, A_NORMAL);
|
|
||||||
wprintw(chatWindow, "%s\n", currentMessage.content);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
wattrset(chatWindow, A_BOLD);
|
|
||||||
wprintw(chatWindow, "<%s>: ", currentMessage.name);
|
|
||||||
wattrset(chatWindow, A_NORMAL);
|
|
||||||
wprintw(chatWindow, "%s\n", currentMessage.content);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redrawClientLayout(gameWindow, chatWindow, inputWindow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================================
|
|
||||||
// | End of receiving-thread.c, copyright notice follows. |
|
|
||||||
// ========================================================
|
|
||||||
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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/>.
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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, ¤tCommand->caller, 1);
|
||||||
|
|
||||||
|
// Queue the outputMessage:
|
||||||
|
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
|
||||||
|
|
||||||
|
//queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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(¤tCommand->arguments[strlen(currentCommand->arguments) + 1]);
|
||||||
|
coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16);
|
||||||
|
if (selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0)
|
||||||
|
{
|
||||||
|
switch (selectedStat)
|
||||||
|
{
|
||||||
|
case WITS:
|
||||||
|
{
|
||||||
|
currentCommand->caller->stats->wits += selectedAmount;
|
||||||
|
strncat(specMessage->messageContent, "Increased wits.", 16);
|
||||||
|
currentCommand->caller->stats->specPoints -= selectedAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INTELLECT:
|
||||||
|
{
|
||||||
|
currentCommand->caller->stats->intellect += selectedAmount;
|
||||||
|
strncat(specMessage->messageContent, "Increased intellect.", 21);
|
||||||
|
currentCommand->caller->stats->specPoints -= selectedAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STRENGTH:
|
||||||
|
{
|
||||||
|
currentCommand->caller->stats->strength += selectedAmount;
|
||||||
|
strncat(specMessage->messageContent, "Increased strength.", 20);
|
||||||
|
currentCommand->caller->stats->specPoints -= selectedAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ENDURANCE:
|
||||||
|
{
|
||||||
|
currentCommand->caller->stats->endurance += selectedAmount;
|
||||||
|
strncat(specMessage->messageContent, "Increased endurance.", 21);
|
||||||
|
currentCommand->caller->stats->specPoints -= selectedAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DEXERITY:
|
||||||
|
{
|
||||||
|
currentCommand->caller->stats->dexerity += selectedAmount;
|
||||||
|
strncat(specMessage->messageContent, "Increased dexerity.", 21);
|
||||||
|
currentCommand->caller->stats->specPoints -= selectedAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INVALID:
|
||||||
|
{
|
||||||
|
strncat(specMessage->messageContent, "Invalid stat.", 21);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strncat(specMessage->messageContent, "You have entered an invalid amount of spec points.", 51);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strncat(specMessage->messageContent, "You have no spec points available.", 35);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate an outputMessage for the queue:
|
||||||
|
outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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] = ¶meters->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, ¤tCommand->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, ¤tCommand->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, ¤tCommand->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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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';
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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(¤tTime));
|
||||||
|
|
||||||
|
// Initialize the sockets to 0, so we don't crash.
|
||||||
|
for (int index = 0; index < PLAYERCOUNT; index++)
|
||||||
|
{
|
||||||
|
clientSockets[index] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a socket and make sure we actually get one.
|
||||||
|
socketFileDesc = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (socketFileDesc == -1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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("");
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue