Alpha 0.5 release of SilverMUD:

Features Changed:
- Refactored old lists and queues to a single type.
- Commented and cleaned up the codebase.
- Various improvements. See commits for more details.

Squashed commit of the following:

commit ff281e5ce6
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Feb 15 22:16:12 2023 +0000

    Increment version number

commit f5cb3ad16e
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Feb 15 22:09:21 2023 +0000

    More cleaning up.

    - Brought remaining files in line with style guides, and improved comments.

commit f31f0c79a5
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Feb 15 21:43:13 2023 +0000

    Cleaned up gamelogic.c/h

    - Improved comments to clarify the purpose and usage of the data structures and functions.
    - Brought the files in line with the SilverMUD style guide.

commit c2af4a551a
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Feb 15 21:30:40 2023 +0000

    Cleaned up inputoutput.h

    - Changed the comments about the data structures to be more descriptive about their function.

commit c753182827
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Feb 15 21:22:14 2023 +0000

    Cleaned up inputoutput.c.

    - Added additional comments in sections functions that were unclear.
    - Renamed targetIndex to sentCount, in order to clarify the usage of the variable.
    - Linted according to the current SilverMUD style guide.

commit f411333203
Author: Barry Kane <barry@omnimenu.ie>
Date:   Mon Feb 13 17:23:30 2023 +0000

    Refactored SilverMUD to use the queue type for command events.

    - Removed the old commandQueue type.
    - Removed the functions relating to creating, pushing, popping, and peeking the commandQueues.
    - Refactored the gameLogicHandler to use a queue type.

commit 602f177a8f
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Feb 12 23:32:39 2023 +0000

    Added some more comments.

    - Commented the data structures in areadata.h and gamelogic.h.

commit d0e4a8f9fc
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Feb 12 23:13:10 2023 +0000

    Removed the body of the try command.

    - Removed the try command's test functionality to allow for the later implementation of the actual functionality.

commit 66e0279e78
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sat Feb 11 00:07:30 2023 +0000

    Commented the data structures in queue.h.

    - Commented all the data structures in queue, to make it slightly clearer as to their usage and what they store.

commit feb1743425
Author: Barry Kane <barry@omnimenu.ie>
Date:   Fri Feb 10 23:33:36 2023 +0000

    Added naming rule 2

commit 52fd7ef6fb
Author: Barry Kane <barry@omnimenu.ie>
Date:   Thu Feb 9 21:53:23 2023 +0000

    Wrote more documentation and a rule for the style guide.

    Documented basic usage of the client's launch options to join a server and added the no one letter variable names rule.

commit a38cbb70a8
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Feb 8 17:15:23 2023 +0000

    Fixed Thomas's Bug.

    In short:
    - When a large amount of input was recieved, the server would hang.
    - The server would hang on queue->lock for the input queue, in pushQueue().
    - Upon debugging, it was revealed that queue->lock was actually false at this time.
    - GCC had optimized out the actual loop, leaving it to get stuck even though the variable had changed.
    - Adding the volatile keyword to the lock fixed this issue.

commit c2c77d6343
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Dec 21 20:31:32 2022 +0000

    Cleaned and styled SilverMUDClient.c

    - Brought SilverMUDClient.c to a consistent style.
    - Cleaned and neatened various parts of SilverMUDClient.c.
    - Minor cleanup of playerdata.h.
    - Began writing the SilverMUD Style Guide.
    - Removed outputQueue-test.c, because outputQueue no longer exists.

commit 6a653c75b9
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Dec 21 00:49:26 2022 +0000

    Implemented proper thread sleeping and additional output thread

    - Replaced previous inefficient "spin-waiting" with proper thread sleeping.
    - Added threading primitives to the queue type to enable this.
    - Added additional thread for output management.
    - Miscellanous cleanup and restructuring.

commit 15d82f59ee
Author: Barry Kane <barry@omnimenu.ie>
Date:   Tue Dec 20 15:55:24 2022 +0000

    Refactored the outputQueue to use the new queue type

    - Patched a bug where I didn't set the correct type for queue nodes.
    - Removed all traces of the previous outputMessageQueue type.
    - Removed the pointer "next" in outputMessage.
    - Rewrote the the main thread and game logic thread to use a queue for output.
    - Refactored outputMessage to allow for a variable amount of playerInfo pointers.

commit 9b3df5928b
Author: Barry Kane <barry@omnimenu.ie>
Date:   Mon Dec 19 23:44:16 2022 +0000

    Refactored the input queue to use the new queue type

    - Removed all traces of the previous inputMessageQueue type.
    - Removed the pointer "next" in inputMessage.
    - Rewrote the the main thread and game logic thread to use a queue for input.

commit 8ae3eaf2b8
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Dec 18 21:07:10 2022 +0000

    Added queue.c and queue.h.

    - Added a singular queue type for input/output messages and commands.
    - Added a test of the queue type in tests.
    - Made some edits to the Makefile to enable the addition of debug code using the preprocessor.
    - Minor styling and spelling updates in gamelogic.c/h and text effects .h.
    - Ready to integrate the new queue type in place of the previous ones.

commit 2ab873b40b
Author: Barry Kane <barry@omnimenu.ie>
Date:   Wed Nov 30 15:31:14 2022 +0000

    Make the client respect the set character delay.

    - The client now respects the character delay set on the command line.

commit 4cc0d3a0f6
Author: Barry Kane <barry@omnimenu.ie>
Date:   Tue Nov 29 21:04:36 2022 +0000

    Completed the conversion to the new linked-list type.

    - Moved all code relating to skills to use the new linked-lists.
    - Removed all old code relating to other lists.
    - Improved linked lists to get nodes more efficiently in the second half of the list.

commit 51f1a953e7
Author: Barry Kane <barry@omnimenu.ie>
Date:   Fri Nov 18 14:44:25 2022 +0000

    Refactored areas to use linked-lists.

    - Refactored the server to rely on the linked-list version of area lists.
    - Removed all old code pertaining to Area/Path lists.
    - Removed a no-longer useful test for corestat-from string performance.y

commit 6b3d9febf6
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Nov 13 18:26:36 2022 +0000

    Linked lists now destroy the pathList of an area.

    - Modified destroyList to destroy pathLists in areas.

commit d843f0b170
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Nov 13 18:21:06 2022 +0000

    Added one-way path function.

    - Added a function to create one way paths.
    - Changed the test areas to a new setting and to include one-way paths.

commit 582a0d02ae
Author: Barry Kane <barry@omnimenu.ie>
Date:   Sun Nov 13 00:23:42 2022 +0000

    Refactored paths to use linked lists.

    - Removed the code for the now-obsolete-before-actually-being-used pathLists.
    - playerAreas now contain a list called pathList.
    - Refactored createArea, createPath, and the /move and /look commands.
    - Added typedefs to prevent the compiler being unable to link.

commit f3ad758e4f
Author: Barry Kane <barry@omnimenu.ie>
Date:   Fri Nov 11 22:58:05 2022 +0000

    Preliminary implementation of linked-list.c

    - Implemented first pass of linked-list.c.

    TODO:
    - Polish linked-list.c 'till you can see a reflection in it.
    - Refactor existing codebase to use linked-list.c.y

commit c68e66e7bc
Author: Barry Kane <barry@omnimenu.ie>
Date:   Mon Oct 31 01:55:44 2022 +0000

    Squashed current warnings.

    - Fixed all current warnings.
    - Added the ability to set the text delay as a command line option in the server.

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

    Incremented version numbering in preperation for merge to master.

    - Incremented version numbering to Alpha 0.4.

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

    Implemented Skill Checks.

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

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

    Completed Reorganization of Area Data

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

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

    Warning fixes and Makefile edits

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

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

    Made client exit gracefully upon server exit:

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

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

    Began implementation of skills and stats.

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

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

    Reorganized file structure.

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

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

    Began implementing game logic and re-implementing commands

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

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

    Removed inputhandling library

    The functionality was moved to inputoutput.

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

    Basic message queuing implemented

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

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

    Cleaned up client.

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

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

    Added initial GnuTLS encryption.

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

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

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

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

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

    Added initial implementation of doubly-linked lists

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

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

    Added area and path creation functions

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

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

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

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

    Incremented Version Number.

    - Incremented version number in preperation for merge.

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

    Added basic name system

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

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

    Increment version message for merge.

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

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

    Added two-window messaging to the client.

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

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

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

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

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

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

    Git Sanity Check
This commit is contained in:
Barry Kane 2023-02-15 22:24:24 +00:00
parent fa46e40860
commit e5ff66a84e
20 changed files with 1858 additions and 1328 deletions

View File

@ -24,6 +24,6 @@ clean:
rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug
all: clean SilverMUDClient SilverMUDServer
all: CFLAGS += -Wall -Wextra -Ofast
debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og
all: CFLAGS += -Wall -Wextra -Ofast
debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og -D debug
debug: clean SilverMUDClientDebug SilverMUDServerDebug

View File

@ -1,28 +1,91 @@
#+LATEX_HEADER: \RequirePackage[left=0.3in,top=0.3in,right=0.3in,bottom=0.3in, a4paper]{geometry}
* SilverMUD: The Hackable Terminal-Top Roleplaying Game.
SilverMUD is a tool for creating engaging and communal stories, all over the
world through the internet. It's designed to give a gamemaster the same power
to improvise that they have at the table, through simple programming and
easy-to-understand structures.
** Player's Guide
*** The Basic Commands
* Player's Guide
** Running The Client
*** How To Connect To A Server:
To connect to a server, use the command-line option =-i=, and the IP address of
the server. 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 | Takes a character name | Logs you into the server with the given character name. |
| MOVE | Takes a path name or a path number | Moves you down the given path. |
| LOOK | None | Gives you a description of what's around you, and what you can do. |
| STAT | None | Displays your current status and character sheet. |
| SPEC | Core stat name | Allows you to apply spec points to a given stat. |
| TRY | Core stat name or skill name and an object number | Attempt to use the given stat or skill on the object. |
| 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. |
** Gamemaster's Guide
*** Running the Server:
* Gamemaster's Guide
** Running the Server:
** Developer's Guide
*** Build Prerequisites:
* Developer's Guide
** Build Prerequisites:
SilverMUD has the following dependencies:
- GnuTLS
- ncurses
** 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.
- () :: These are parentheses.
- [] :: These are brackets.
- {} :: These are braces.
*** Formatting:
**** Control Statements:
- A space should be between the keyword and the condition. This is to make
control statements visually distinct from function calls.
- Opening braces should be on the line after the control statement, and closing
braces on the line after the last statement, on it's own. This is to make the
scope of the control statement easily identifiable.
- else and else if should always be on a new line, not the same line as an if
statement's closing brace. This is to more easily distinguish the seperate
blocks.
- Control statements should never omit braces and do single statements. This is
mostly personal preference, but I do think it makes things more clear.
*** Naming:
**** Rule 0: NEVER USE i AND j!
Never use the variable names i and j. These are easy to confuse, and often make
nested loops awful to read. Name these more descriptively.
For example:
- If you are using a variable to index an array, name the variable index.
- If you are indexing multiple arrays, name it "array name + Index".
- If you are using it to count something, call it count, or "name of the
thing you are counting + count".
**** Rule 1: No one letter variable names, unless in a mathematical function.
You should never use one letter variable names. They're needlessly obtuse and
you will not remember their meaning upon re-reading of the source code. The
exception to this is when you are writing a function which replicates a
mathematical formula or function with commonly accepted notation. However, you
should consider if it would be better to break mathematical convention for
clarity inside the program, such as when the variable names are the first letter
of a word or the mathematical notation uses many similar looking variables.
**** Rule 2: Prefer to use full words in variable and function names:
You should always prefer to use full words in variable and function names. It
makes the source code much easier to read, like a sentence. Ideally, if you want
to shorten the name, use synonyms or rephrasing before you resort to removing
letters.
*** Comments:

View File

@ -3,6 +3,7 @@
#include <string.h>
#include "areadata.h"
#include "playerdata.h"
#include "linkedlist.h"
// ====================
// -=[ Area/Paths: ]=-:
@ -16,18 +17,15 @@ playerArea * createArea(char * nameString, char * descriptionString)
// Copy the strings into the newly created area:
strncpy(createdArea->areaName, nameString, 32 - 1);
strncpy(createdArea->areaDescription, descriptionString, MAX - 35);
strncpy(createdArea->areaDescription, descriptionString, MAX - 36);
// Properly null-terminate the strings:
createdArea->areaName[31] = '\0';
createdArea->areaDescription[MAX] = '\0';
// Ensure that all the paths are set to NULL:
for(int index = 0; index < 16; index++)
{
createdArea->areaExits[index] = NULL;
}
createdArea->areaDescription[MAX - 36] = '\0';
// Create a list for the paths in the area:
createdArea->pathList = createList(PATH);
// Return the pointer:
return createdArea;
}
@ -35,183 +33,40 @@ playerArea * createArea(char * nameString, char * descriptionString)
// Create a path between two areas given two areas and two strings:
int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription)
{
int fromAreaSlot, toAreaSlot;
for(fromAreaSlot = 0; fromAreaSlot < 16; fromAreaSlot++)
{
if(fromArea->areaExits[fromAreaSlot] == NULL)
{
break;
}
if((fromArea->areaExits[fromAreaSlot] != NULL) && (fromAreaSlot == 15))
{
return 1;
}
}
for(toAreaSlot = 0; toAreaSlot < 32; toAreaSlot++)
{
if(toArea->areaExits[toAreaSlot] == 0)
{
break;
}
if((toArea->areaExits[toAreaSlot] != 0) && (toAreaSlot == 31))
{
return 2;
}
}
// Allocate the new paths:
playerPath * fromPath = malloc(sizeof(playerPath));
playerPath * toPath = malloc(sizeof(playerPath));
fromArea->areaExits[fromAreaSlot] = fromPath;
toArea->areaExits[toAreaSlot] = toPath;
// Setup the from path:
strncpy(fromPath->pathName, fromDescription, 32 - 1);
fromPath->pathName[31] = '\0';
fromPath->pathName[31] = '\0';
fromPath->areaToJoin = toArea;
// Setup the to path:
strncpy(toPath->pathName, toDescription, 32 - 1);
toPath->pathName[31] = '\0';
fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea;
toArea->areaExits[toAreaSlot]->areaToJoin = fromArea;
toPath->pathName[31] = '\0';
toPath->areaToJoin = fromArea;
// Add to the lists:
addToList(fromArea->pathList, fromPath, PATH);
addToList(toArea->pathList, toPath, PATH);
return 0;
}
// =========================
// -=[ Area/Path Lists: ]=-:
// =========================
// Create and initialize an areaList:
areaNode * createAreaList(playerArea * initialArea)
// Create a one-way path between two areas given two areas and a string:
int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description)
{
areaNode * newAreaList = malloc(sizeof(areaNode));
newAreaList->data = initialArea;
newAreaList->next = NULL;
newAreaList->prev = NULL;
return newAreaList;
}
// Allocate the new paths:
playerPath * path = calloc(1, sizeof(playerPath));
// Create and initialize an pathList:
pathNode * createPathList(playerPath * initialPath)
{
pathNode * newPathList = malloc(sizeof(pathNode));
newPathList->data = initialPath;
newPathList->next = NULL;
newPathList->prev = NULL;
return newPathList;
}
// Setup the path:
strncpy(path->pathName, description, 32 - 1);
path->pathName[31] = '\0';
path->areaToJoin = toArea;
// Adds an areaNode to the end of a list, returning it's position:
int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd)
{
areaNode * current;
int index = 0;
current = toList;
while(current->next != NULL)
{
current = current->next;
index++;
}
current->next = malloc(sizeof(areaNode));
current->next->prev = current;
current->next->data = areaToAdd;
current->next->next = NULL;
// Add to the list:
addToList(fromArea->pathList, path, PATH);
return 0;
}
// Removes an areaNode from the list, returning 0 on success and -1 on failure:
int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete)
{
areaNode * current = fromList;
while(current->data != areaToDelete && current->next != NULL)
{
current = current->next;
}
if(current->next == NULL && current->data != areaToDelete)
{
return -1;
}
current->prev->next = current->next;
if(current->next != NULL)
{
current->next->prev = current->prev;
}
free(current);
return 0;
}
// Adds an pathNode to the end of a list, returning it's position:
int addPathNodeToList(pathNode * toList, playerPath * pathToAdd)
{
pathNode * current;
int index = 0;
current = toList;
while(current->next != NULL)
{
current = current->next;
index++;
}
current->next = malloc(sizeof(pathNode));
current->next->prev = current;
current->next->data = pathToAdd;
current->next->next = NULL;
return index;
}
// Removes an pathNode from the list, returning 0 on success and -1 on failure:
int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete)
{
pathNode * current = fromList;
while(current->data != pathToDelete || current->next != NULL)
{
current = current->next;
}
if(current->next == NULL && current->data != pathToDelete)
{
return -1;
}
current->prev->next = current->next;
if(current->next != NULL)
{
current->next->prev = current->prev;
}
free(current);
return 0;
}
// Return the areaNode at the given index from the list:
areaNode * getAreaNode(areaNode * fromList, int listIndex)
{
areaNode * current = fromList;
for(int index = 0; index < listIndex; index++)
{
if(current->next != NULL)
{
current = current->next;
}
else
{
return NULL;
}
}
return current;
}
// Return the pathNode at the given index from the list:
pathNode * getPathNode(pathNode * fromList, int listIndex)
{
pathNode * current = fromList;
for(int index = 0; index < listIndex; index++)
{
if(current->next != NULL)
{
current = current->next;
}
else
{
return NULL;
}
}
return current;
}
// Return the playerArea of the areaNode at the given index from the list:
playerArea * getAreaFromList(areaNode * fromList, int listIndex)
{
areaNode * current = getAreaNode(fromList, listIndex);
return current->data;
}

View File

@ -3,79 +3,39 @@
#ifndef AREADATA_H
#define AREADATA_H
#include "constants.h"
#include "linkedlist.h"
// ====================
// -=[ Area/Paths: ]=-:
// ====================
// Let the compiler know that we're going to define these types:
typedef struct playerPath playerPath;
typedef struct playerArea playerArea;
// 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];
playerPath * areaExits[16];
};
// Create an area given a name and description:
// 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:
// 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);
// =========================
// -=[ Area/Path Lists: ]=-:
// =========================
typedef struct areaNode areaNode;
typedef struct pathNode pathNode;
struct pathNode
{
playerPath * data;
pathNode * next;
pathNode * prev;
};
struct areaNode
{
playerArea * data;
areaNode * next;
areaNode * prev;
};
// Create and initialize an areaList:
areaNode * createAreaList(playerArea * initialArea);
// Create and initialize an pathList:
pathNode * createPathList(playerPath * initialPath);
// Adds an areaNode to the end of a list, returning it's position:
int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd);
// Removes an areaNode from the list, returning 0 on success and -1 on failure:
int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete);
// Adds an pathNode to the end of a list, returning it's position:
int addPathNodeToList(pathNode * toList, playerPath * pathToAdd);
// Removes an pathNode from the list, returning 0 on success and -1 on failure:
int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete);
// Return the areaNode at the given index from the list:
areaNode * getAreaNode(areaNode * fromList, int listIndex);
// Return the pathNode at the given index from the list:
pathNode * getPathNode(pathNode * fromList, int listIndex);
// Return the playerArea of the areaNode at the given index from the list:
playerArea * getAreaFromList(areaNode * fromList, int listIndex);
// 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); */

View File

@ -1,4 +1,4 @@
// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.4.
// 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>
@ -14,18 +14,20 @@
#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 paramaters for a thread so we can pass them using a void pointer:
// 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;
} threadparameters;
// Use sockaddr as a type:
@ -34,17 +36,22 @@ typedef struct sockaddr sockaddr;
// A globally available exit boolean:
bool shouldExit = false;
// A function for managing the sending thread:
void * messageSender(void * parameters)
{
struct threadparameters *threadParameters = parameters;
threadparameters * threadParameters = parameters;
gnutls_session_t tlsSession = threadParameters->tlsSession;
FILE * loggingStream = threadParameters->loggingStream;
bool loggingFlag = threadParameters->loggingFlag;
WINDOW * window = threadParameters->window;
userMessage sendBuffer;
// Repeatedly get input from the user, place it in a userMessage, and send it to the server:
while (!shouldExit)
{
// Print the prompt:
wprintw(threadParameters->window, "\n\n\nCOMM-LINK> ");
if (wgetnstr(threadParameters->window, sendBuffer.messageContent, MAX) == ERR)
wprintw(window, "\n\n\nCOMM-LINK> ");
if (wgetnstr(window, sendBuffer.messageContent, MAX) == ERR)
{
// Quit if there's any funny business with getting input:
pthread_exit(NULL);
@ -57,78 +64,104 @@ void * messageSender(void * parameters)
}
// Send the message to the log if logging is enabled:
if (threadParameters->loggingFlag == true)
if (loggingFlag == true)
{
fputs(sendBuffer.messageContent, threadParameters->loggingStream);
fputs("\n", threadParameters->loggingStream);
fflush(threadParameters->loggingStream);
fputs(sendBuffer.messageContent, loggingStream);
fputs("\n", loggingStream);
fflush(loggingStream);
}
// Send the message off to the server:
messageSend(threadParameters->tlsSession, &sendBuffer);
messageSend(tlsSession, &sendBuffer);
}
// 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;
struct threadparameters *threadParameters = parameters;
int screenWidth = getmaxx(threadParameters->window);
// Repeatedly take messages from the server and print them to the chat log window:
while (!shouldExit)
{
returnValue = messageReceive(threadParameters->tlsSession, &receiveBuffer);
// Get the next message:
returnValue = messageReceive(tlsSession, &receiveBuffer);
// Check we haven't been disconnected:
if(returnValue == -10 || returnValue == 0)
if (returnValue == -10 || returnValue == 0)
{
shouldExit = true;
}
// Check if it's a server message:
else if (receiveBuffer.senderName[0] == '\0')
{
wrapString(receiveBuffer.messageContent,
strlen(receiveBuffer.messageContent) - 1, screenWidth);
// Check if it's a command to disconnect:
if (receiveBuffer.messageContent[0] == '\0')
{
shouldExit = true;
pthread_exit(NULL);
}
if(serverMessage == false)
// 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 --====<>====--", 4000, threadParameters->window, true);
slowPrintNcurses("\n --====<>====--", characterDelay, window, true);
serverMessage = true;
}
slowPrintNcurses("\n", 4000, threadParameters->window, true);
slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false);
slowPrintNcurses("\n", 4000, threadParameters->window, 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
{
wrapString(receiveBuffer.messageContent,
strlen(receiveBuffer.messageContent) - 1,
// Fit the string to the screen:
wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1,
screenWidth - strlen(receiveBuffer.senderName) - 2);
if (threadParameters->loggingFlag == true)
// If the user has requested logging, insert the message into the file:
if (loggingFlag == true)
{
fputs(receiveBuffer.senderName, threadParameters->loggingStream);
fputs(": ", threadParameters->loggingStream);
fputs(receiveBuffer.messageContent, threadParameters->loggingStream);
fflush(threadParameters->loggingStream);
fputs(receiveBuffer.senderName, loggingStream);
fputs(": ", loggingStream);
fputs(receiveBuffer.messageContent, loggingStream);
fflush(loggingStream);
}
if(serverMessage == true)
// If we're in a block of server messages, end it:
if (serverMessage == true)
{
slowPrintNcurses("\n --====<>====-- \n", 4000, threadParameters->window, true);
slowPrintNcurses("\n --====<>====-- \n", characterDelay, window, true);
serverMessage = false;
}
slowPrintNcurses(receiveBuffer.senderName, 4000, threadParameters->window, true);
slowPrintNcurses(": ", 4000, threadParameters->window, true);
slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, 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);
}
@ -148,61 +181,61 @@ int main(int argc, char ** argv)
bool chatLogging = false, gameLogging = false;
// Print welcome message:
slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.4\n", 5000);
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':
{
strncpy(ipAddress, optarg, 32);
break;
}
case 'c':
{
strncpy(chatLogPath, optarg, PATH_MAX + 1);
chatLog = fopen(chatLogPath, "a+");
if (chatLog == NULL)
case 'i':
{
chatLogging = false;
memcpy(ipAddress, optarg, 32);
break;
}
else
case 'c':
{
chatLogging = true;
memcpy(chatLogPath, optarg, PATH_MAX + 1);
chatLog = fopen(chatLogPath, "a+");
if (chatLog == NULL)
{
chatLogging = false;
}
else
{
chatLogging = true;
}
break;
}
break;
}
case 'g':
{
strncpy(gameLogPath, optarg, PATH_MAX + 1);
gameLog = fopen(gameLogPath, "a+");
if (gameLog == NULL)
case 'g':
{
gameLogging = false;
memcpy(gameLogPath, optarg, PATH_MAX + 1);
gameLog = fopen(gameLogPath, "a+");
if (gameLog == NULL)
{
gameLogging = false;
}
else
{
gameLogging = true;
}
break;
}
else
case 'p':
{
gameLogging = true;
port = atoi(optarg);
break;
}
case 'd':
{
characterDelay = atoi(optarg);
break;
}
case '?':
{
return 1;
break;
}
break;
}
case 'p':
{
port = atoi(optarg);
break;
}
case 'd':
{
characterDelay = atoi(optarg);
break;
}
case '?':
{
return 1;
break;
}
}
}
@ -217,9 +250,8 @@ int main(int argc, char ** argv)
{
slowPrint("Socket successfully created.\n", characterDelay);
}
bzero(&serverAddress, sizeof(serverAddress));
// Set our IP Address and port. Default to localhost for testing:
// 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);
@ -230,17 +262,11 @@ int main(int argc, char ** argv)
slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay);
exit(0);
}
else
{
slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", characterDelay);
}
usleep(100000);
// Setup a GnuTLS session and initialize it:
gnutls_session_t tlsSession = NULL;
if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0)
{
// Failure Case
{
exit(EXIT_FAILURE);
}
@ -256,7 +282,7 @@ int main(int argc, char ** argv)
// 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:
// Repeatedly attempt to handshake unless we encounter a fatal error:
int returnValue = -1;
do
{
@ -278,6 +304,7 @@ int main(int argc, char ** argv)
logArea->window = newwin(LINES - 5, COLS - 2, 1, 1);
logArea->tlsSession = tlsSession;
logArea->loggingFlag = chatLogging;
logArea->characterDelay = characterDelay;
if (chatLog != NULL)
{
logArea->loggingStream = chatLog;

View File

@ -5,9 +5,11 @@
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#include "queue.h"
#include "constants.h"
#include "gamelogic.h"
#include "playerdata.h"
#include "linkedlist.h"
#include "inputoutput.h"
// =======================
@ -15,92 +17,84 @@
// =======================
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicLoop(void * parameters)
void * gameLogicHandler(void * parameters)
{
gameLogicParameters * threadParameters = parameters;
inputMessage * currentInput = NULL;
commandQueue * commandQueue = createCommandQueue();
while(true)
queue * commandQueue = createQueue();
while (true)
{
// Evaluate remaining commands:
if(commandQueue->currentLength != 0)
while (commandQueue->itemCount != 0)
{
evaluateNextCommand(threadParameters, commandQueue);
}
// Check for new messages and pop them off the queue:
if(threadParameters->inputQueue->currentLength != 0)
// Wait if there is nothing to do:
if (threadParameters->inputQueue->itemCount == 0)
{
while(threadParameters->inputQueue->lock == true);
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 = peekInputMessage(threadParameters->inputQueue);
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] == '/')
if (currentInput->content->messageContent[0] == '/')
{
queueMessagedCommand(commandQueue, currentInput);
}
else if(currentInput->sender->currentArea == getAreaFromList(threadParameters->areaList, 0))
{
currentInput = NULL;
threadParameters->inputQueue->lock = false;
dequeueInputMessage(threadParameters->inputQueue);
}
else
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);
// Create an array of players in the same area to receive the message:
playerInfo ** recipients = malloc(sizeof(playerInfo*) * *threadParameters->playerCount);
for(int index = 0; index < *threadParameters->playerCount; index++)
currentInput->content->senderName[31] = '\0';
// Allocate an array of playerInfo to store the current players in the area for the output message:
playerInfo ** recipients = malloc(sizeof(playerInfo*) * PLAYERCOUNT);
// Initialize them all to NULL:
for (int index = 0; index < PLAYERCOUNT; index++)
{
recipients[index] = NULL;
}
int recipientCount = 0;
for(int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++)
// 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)
if (threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea)
{
recipients[recipientCount] = &threadParameters->connectedPlayers[playerIndex];
recipientCount++;
recipients[recipientIndex] = &threadParameters->connectedPlayers[playerIndex];
recipientIndex++;
}
}
if(currentInput->content->messageContent[0] != '\n')
{
queueTargetedOutputMessage(threadParameters->outputQueue, currentInput->content, recipients, recipientCount);
}
// 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);
}
currentInput = NULL;
threadParameters->inputQueue->lock = false;
dequeueInputMessage(threadParameters->inputQueue);
popQueue(threadParameters->inputQueue);
}
}
pthread_exit(NULL);
}
// Create a commandQueue:
commandQueue * createCommandQueue(void)
{
commandQueue * newCommandQueue = calloc(1, sizeof(commandQueue));
newCommandQueue->back = NULL;
newCommandQueue->front = NULL;
newCommandQueue->lock = false;
newCommandQueue->paused = false;
newCommandQueue->currentLength = 0;
return newCommandQueue;
}
// Return the front commandEvent from a commandQueue:
commandEvent * peekCommand(commandQueue * queue)
{
// Do nothing until the command queue is unlocked.
while(queue->lock);
// Return the front item.
return queue->front;
}
// Enqueue a messaged command to a commandQueue:
int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue)
// Evaluate the next commandEvent in a queue:
void queueMessagedCommand(queue * queue, inputMessage * messageToQueue)
{
// Prepare the new commandEvent:
commandEvent * newCommand = calloc(1, sizeof(commandEvent));
@ -112,60 +106,29 @@ int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue)
strtok(messageToQueue->content->messageContent, " ");
// Copy the command and arguments to the new commandEvent:
strncpy(newCommand->command, &messageToQueue->content->messageContent[1], 16);
strncpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2],
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);
}
// Wait for the queue to unlock:
while (queue->lock);
// Check that we're not overflowing the queue:
if ((queue->currentLength + 1) > MAXQUEUELENGTH)
{
// Unlock the queue:
queue->lock = false;
return -1;
}
else
{
// If the queue is empty, set the first commandEvent as both the front and back of the queue:
if(queue->front == NULL)
{
queue->front = newCommand;
queue->back = newCommand;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
else
{
queue->back->next = newCommand;
queue->back = newCommand;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
}
pushQueue(queue, newCommand, COMMAND);
}
// Enqueue a command to a commandQueue:
int queueCommand(commandQueue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer)
// 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));
@ -175,171 +138,72 @@ int queueCommand(commandQueue * queue, char * command, char * arguments, int com
// Copy the command and arguments:
strncpy(newCommand->command, command, commandLength);
strncpy(newCommand->arguments, arguments, argumentsLength);
if (argumentsLength > 0)
{
strncpy(newCommand->arguments, arguments, argumentsLength);
}
// Ensure the arguments are safe to parse, without adding newlines:
userNameSanatize(newCommand->command, 16);
// Wait for the queue to unlock:
while (queue->lock);
// Check that we're not overflowing the queue:
if ((queue->currentLength + 1) > MAXQUEUELENGTH)
{
// Unlock the queue:
queue->lock = false;
return -1;
}
else
{
// If the queue is empty, set the first commandEvent as both the front and back of the queue:
if(queue->front == NULL)
{
queue->front = newCommand;
queue->back = newCommand;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
else
{
queue->back->next = newCommand;
queue->back = newCommand;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
}
pushQueue(queue, newCommand, COMMAND);
}
// Dequeue the front commandEvent from a commandQueue:
int dequeueCommand(commandQueue * queue)
// Evaluate the next commandEvent in a queue:
int evaluateNextCommand(gameLogicParameters * parameters, queue * queue)
{
// Wait for the queue to unlock:
commandEvent * currentCommand = peekQueue(queue)->data.command;
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check the list isn't empty:
if(queue->front == NULL)
{
queue->lock = false;
return -1;
}
// If there is only one item in the queue:
else if(queue->front == queue->back)
{
free(queue->front->command);
free(queue->front->arguments);
free(queue->front);
queue->front = NULL;
queue->back = NULL;
queue->currentLength--;
queue->lock = false;
return 0;
}
// Remove the front item:
else
{
commandEvent * commandToDelete = queue->front;
queue->front = queue->front->next;
free(commandToDelete->command);
free(commandToDelete->arguments);
free(commandToDelete);
queue->currentLength--;
queue->lock = false;
return 0;
}
}
// Evaluate the next commandEvent:
int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
{
commandEvent * currentCommand = peekCommand(queue);
while(queue->lock);
queue->lock = true;
if(currentCommand == NULL)
if (currentCommand == NULL)
{
return -1;
}
// Try command: Attempt to use a stat or skill on an object:
if(strncmp(currentCommand->command, "try", 3) == 0)
if (strncmp(currentCommand->command, "try", 3) == 0)
{
userMessage * tryMessage = malloc(sizeof(userMessage));
tryMessage->senderName[0] = '\0';
switch (getCoreStatFromString(currentCommand->arguments, 9))
{
case STRENGTH:
{
switch (statCheck(currentCommand->caller, 20, STRENGTH))
{
case CRITICAL_FAILURE:
{
strcpy(tryMessage->messageContent, "You weak, puny shit. Bet you don't even lift, bro.\n");
break;
}
case FAILURE:
{
strcpy(tryMessage->messageContent, "Come on, bro, you should be able to get this set done.\n");
break;
}
case SUCCESS:
{
strcpy(tryMessage->messageContent, "Nice set, bro. Keep it up.\n");
break;
}
case CRITICAL_SUCCESS:
{
strcpy(tryMessage->messageContent, "HOLY SHIT, BRO! THAT'S SOME MAD REPS RIGHT THERE!\n");
break;
}
default:
{
strcpy(tryMessage->messageContent, "I don't even, bro.\n");
}
}
break;
}
default:
{
sprintf(tryMessage->messageContent,"%d",
skillCheck(currentCommand->caller, 10, currentCommand->arguments, strlen(currentCommand->arguments),
parameters->globalSkillList));
break;
}
}
queueTargetedOutputMessage(parameters->outputQueue, tryMessage, &currentCommand->caller, 1);
// Temporary message until we can implement objects, events, and challenges.
strcpy(tryMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n");
// Allocate an outputMessage for the queue:
outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, tryOutputMessage, OUTPUT_MESSAGE);
// Free the userMessage:
free(tryMessage);
}
// Exit command: Sends an "empty" exit message to disconnect a client:
if(strncmp(currentCommand->command, "exit", 4) == 0)
if (strncmp(currentCommand->command, "exit", 4) == 0)
{
// Allocate a userMessage containing null characters as the first char in both fields:
userMessage * exitMessage = malloc(sizeof(userMessage));
exitMessage->senderName[0] = '\0';
exitMessage->messageContent[0] = '\0';
queueTargetedOutputMessage(parameters->outputQueue, exitMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
outputMessage * exitOutputMessage = createTargetedOutputMessage(exitMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, exitOutputMessage, OUTPUT_MESSAGE);
// Free the userMessage
free(exitMessage);
}
// Move command: Moves the caller to a different area given a path name or number:
if(strncmp(currentCommand->command, "move", 4) == 0)
if (strncmp(currentCommand->command, "move", 4) == 0)
{
char requestedPath[32];
if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getAreaFromList(parameters->areaList, 0))
if (strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area)
{
strncpy(requestedPath, currentCommand->arguments, 32);
memcpy(requestedPath, currentCommand->arguments, 32);
userNameSanatize(requestedPath, 32);
requestedPath[31] = '\0';
if(movePlayerToArea(currentCommand->caller, requestedPath) == 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:
@ -351,7 +215,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
}
// Look command: Returns the description of the current area and paths:
if(strncmp(currentCommand->command, "look", 4) == 0)
if (strncmp(currentCommand->command, "look", 4) == 0)
{
char formattedString[64];
userMessage * lookMessage = calloc(1, sizeof(userMessage));
@ -359,45 +223,69 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33);
strncat(lookMessage->messageContent, "\n", 2);
strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35);
queueTargetedOutputMessage(parameters->outputQueue, lookMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
outputMessage * lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
//queueTargetedOutputMessage(parameters->outputQueue, lookMessage, &currentCommand->caller, 1);
bzero(lookMessage, sizeof(userMessage));
if(currentCommand->caller->currentArea->areaExits[0] != NULL)
// 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)
{
strncat(lookMessage->messageContent, "You can go:", 13);
for(int index = 0; index < 16; index++)
for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++)
{
if(currentCommand->caller->currentArea->areaExits[index] != NULL)
if ((charCount + 64) >= MAX)
{
snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentCommand->caller->currentArea->areaExits[index]->pathName);
strncat(lookMessage->messageContent, formattedString, 64);
lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
bzero(lookMessage, 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;
}
queueTargetedOutputMessage(parameters->outputQueue, lookMessage, &currentCommand->caller, 1);
// Allocate another outputMessage for the queue:
lookOutputMessage = createTargetedOutputMessage(lookMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE);
}
free(lookMessage);
}
// Join command: Allows the player to join the game given a name:
// TODO: Implement login/character creation. Will be a while:
if(strncmp(currentCommand->command, "join", 4) == 0)
if (strncmp(currentCommand->command, "join", 4) == 0)
{
if(currentCommand->caller->currentArea == getAreaFromList(parameters->areaList, 0))
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')
if (currentCommand->arguments[0] == '\0')
{
validName = false;
}
if(strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0)
if (strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0)
{
validName = false;
}
}
if(validName)
if (validName)
{
strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16);
currentCommand->caller->currentArea = getAreaFromList(parameters->areaList, 1);
currentCommand->caller->currentArea = getFromList(parameters->areaList, 1)->area;
// 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;
@ -407,20 +295,12 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
}
}
// Talk command: Allows the player to begin a chat session with another player:
if(strncmp(currentCommand->command, "talk", 4) == 0)
if (strncmp(currentCommand->command, "talk", 4) == 0)
{
// TODO: Implement.
}
if(strncmp(currentCommand->command, "skillissue", 10) == 0)
{
userMessage * statMessage = calloc(1, sizeof(userMessage));
statMessage->senderName[0] = '\0';
strcpy(statMessage->messageContent, "Have you tried getting good?");
queueTargetedOutputMessage(parameters->outputQueue, statMessage, &currentCommand->caller, 1);
free(statMessage);
}
// Stat command: Displays the current character's sheet.
if(strncmp(currentCommand->command, "stat", 4) == 0)
if (strncmp(currentCommand->command, "stat", 4) == 0)
{
char * formattedString = calloc(121, sizeof(char));
userMessage * statMessage = calloc(1, sizeof(userMessage));
@ -440,7 +320,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
strncat(statMessage->messageContent, formattedString, 120);
// Levelling stats: Current XP, and spec points.
if(currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0)
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);
@ -450,29 +330,39 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience);
}
strncat(statMessage->messageContent, formattedString, 120);
// Allocate an outputMessage for the queue:
outputMessage * statOutputMessage = createTargetedOutputMessage(statMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE);
queueTargetedOutputMessage(parameters->outputQueue, statMessage, &currentCommand->caller, 1);
bzero(statMessage->messageContent, sizeof(char) * MAX);
if(currentCommand->caller->skills->head != NULL)
if (currentCommand->caller->skills->head != NULL)
{
skillNode * currentSkill = currentCommand->caller->skills->head;
size_t skillIndex = 0;
int charCount = 0;
bool addNewline = false;
while(currentSkill != NULL)
playerSkill * skill;
while (skillIndex < currentCommand->caller->skills->itemCount)
{
snprintf(formattedString, 120, "| %2d | %31s ",
currentSkill->skill->skillPoints, currentSkill->skill->skillName);
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)
if ((charCount + 43) >= MAX)
{
strncat(statMessage->messageContent, "\n", 2);
queueTargetedOutputMessage(parameters->outputQueue, statMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
statOutputMessage = createTargetedOutputMessage(statMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE);
bzero(statMessage, sizeof(userMessage));
charCount = 0;
break;
}
else if(addNewline)
else if (addNewline)
{
strncat(statMessage->messageContent, "|\n", 3);
charCount++;
@ -481,29 +371,31 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
else
{
addNewline = true;
}
currentSkill = currentSkill->next;
}
}
queueTargetedOutputMessage(parameters->outputQueue, statMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
statOutputMessage = createTargetedOutputMessage(statMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE);
}
free(statMessage);
free(formattedString);
}
// Spec command: Assign spec points to stats:
if(strncmp(currentCommand->command, "spec", 4) == 0)
if (strncmp(currentCommand->command, "spec", 4) == 0)
{
userMessage * specMessage = calloc(1, sizeof(userMessage));
specMessage->senderName[0] = '\0';
char * formattedString = calloc(121, sizeof(char));
if(currentCommand->caller->stats->specPoints > 0)
if (currentCommand->caller->stats->specPoints > 0)
{
int selectedAmount = 0;
strtok(currentCommand->arguments, " ");
selectedAmount = atoi(&currentCommand->arguments[strlen(currentCommand->arguments) + 1]);
coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16);
if(selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0)
if (selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0)
{
switch (selectedStat)
{
@ -558,8 +450,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
strncat(specMessage->messageContent, "You have no spec points available.", 35);
}
// Send the message:
queueTargetedOutputMessage(parameters->outputQueue, specMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, specOutputMessage, OUTPUT_MESSAGE);
// Show the new stat sheet:
queue->lock = false;
@ -570,11 +465,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
free(specMessage);
free(formattedString);
}
if(strncmp(currentCommand->command, "skill", 5) == 0)
if (strncmp(currentCommand->command, "skill", 5) == 0)
{
userMessage * skillMessage = calloc(1, sizeof(userMessage));
skillMessage->senderName[0] = '\0';
if((currentCommand->caller->stats->skillPoints - 1) >= 0)
if ((currentCommand->caller->stats->skillPoints - 1) >= 0)
{
int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments,
strlen(currentCommand->arguments), currentCommand->caller);
@ -599,29 +494,42 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
{
strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n");
}
queueTargetedOutputMessage(parameters->outputQueue, skillMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
outputMessage * skillOutputMessage = createTargetedOutputMessage(skillMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, skillOutputMessage, OUTPUT_MESSAGE);
free(skillMessage);
}
if(strncmp(currentCommand->command, "listskills", 10) == 0)
if (strncmp(currentCommand->command, "listskills", 10) == 0)
{
skillNode * currentSkill = parameters->globalSkillList->head;
userMessage * listMessage = calloc(1, sizeof(userMessage));
char * formattedString = calloc(121, sizeof(char));
int charCount = 0;
size_t skillIndex = 0;
bool addNewline = false;
while(currentSkill != NULL)
playerSkill * currentSkill;
while (skillIndex < parameters->globalSkillList->itemCount)
{
snprintf(formattedString, 120, "| %-31s ", currentSkill->skill->skillName);
currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill;
snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName);
charCount += 43;
strncat(listMessage->messageContent, formattedString, 120);
if((charCount + 46) >= MAX)
if ((charCount + 46) >= MAX)
{
queueTargetedOutputMessage(parameters->outputQueue, listMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE);
bzero(listMessage, sizeof(userMessage));
charCount = 0;
addNewline = false;
}
else if(addNewline)
else if (addNewline)
{
strncat(listMessage->messageContent, "|\n", 3);
charCount++;
@ -631,24 +539,28 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue)
{
addNewline = true;
}
currentSkill = currentSkill->next;
skillIndex++;
}
queueTargetedOutputMessage(parameters->outputQueue, listMessage, &currentCommand->caller, 1);
// Allocate an outputMessage for the queue:
outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, &currentCommand->caller, 1);
// Queue the outputMessage:
pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE);
free(listMessage);
free(formattedString);
}
// Remove the current command and unlock the queue:
currentCommand = NULL;
queue->lock = false;
dequeueCommand(queue);
popQueue(queue);
return 0;
}
// Run a stat check:
// 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)
if (chance > 100 || chance < 0)
{
return ERROR;
}
@ -689,9 +601,9 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck)
}
}
int attempt = (random() % 100) + modifier;
if(attempt >= chance)
if (attempt >= chance)
{
if(attempt >= 98)
if (attempt >= 98)
{
return CRITICAL_SUCCESS;
}
@ -702,7 +614,7 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck)
}
else
{
if(attempt <= 2)
if (attempt <= 2)
{
return CRITICAL_FAILURE;
}
@ -713,11 +625,11 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck)
}
}
// Run a skill check:
outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList)
// 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)
if (chance > 100 || chance < 0)
{
return ERROR;
}
@ -725,53 +637,46 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski
// Check if the player has the given skill:
bool playerHasSkill = false;
skillNode * currentPlayerNode = player->skills->head;
while(currentPlayerNode != NULL)
size_t playerIndex = 0;
while (playerIndex < player->skills->itemCount)
{
if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0)
if (strncmp(skillName, getFromList(player->skills, playerIndex)->skill->skillName, skillNameLength) != 0)
{
playerHasSkill = true;
break;
}
currentPlayerNode = currentPlayerNode->next;
playerIndex++;
}
// If the player doesn't have the skill, check if it's in the game and is trained:
bool trainedSkill = false;
if(!playerHasSkill)
size_t globalIndex = 0;
while (globalIndex < globalSkillList->itemCount)
{
skillNode * currentNode = globalSkillList->head;
while(strncmp(skillName, currentNode->skill->skillName, 32) != 0)
if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) != 0)
{
if(currentNode->next == NULL)
{
fprintf(stderr, "Skill doesn't exist in skill list.\n");
return ERROR;
}
currentNode = currentNode->next;
}
if(currentNode->skill->trainedSkill == true)
{
trainedSkill = true;
trainedSkill = getFromList(globalSkillList, globalIndex)->skill->trainedSkill;
break;
}
globalIndex++;
}
// Calculate the modifier:
int modifier = 0;
if(trainedSkill)
if (trainedSkill)
{
modifier = -100;
}
else
else if (playerHasSkill)
{
modifier = currentPlayerNode->skill->skillPoints * 4;
modifier = getFromList(player->skills, playerIndex)->skill->skillModifier * 4;
}
// Attempt the check:
int attempt = (random() % 100) + modifier;
if(attempt >= chance)
if (attempt >= chance)
{
if(attempt >= 98)
if (attempt >= 98)
{
return CRITICAL_SUCCESS;
}
@ -782,7 +687,7 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski
}
else
{
if(attempt <= 2)
if (attempt <= 2)
{
return CRITICAL_FAILURE;
}
@ -793,17 +698,17 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski
}
}
// Move a player to a different area given a path in the area:
// Move a player along a path in their current area:
int movePlayerToArea(playerInfo * player, char * requestedPath)
{
// Check if a number was given first:
int selected = atoi(requestedPath);
if(selected != 0)
size_t selected = atoi(requestedPath);
if (selected != 0 && !(selected > player->currentArea->pathList->itemCount))
{
if(player->currentArea->areaExits[selected - 1] != NULL &&
player->currentArea->areaExits[selected - 1]->areaToJoin != NULL)
if (getFromList(player->currentArea->pathList, selected - 1)->path != NULL &&
getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL)
{
player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin;
player->currentArea = getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin;
return 0;
}
else
@ -813,17 +718,15 @@ int movePlayerToArea(playerInfo * player, char * requestedPath)
}
// Otherwise search for the description:
for (int index = 0; index < 16; index++)
for (size_t index = 0; index < player->currentArea->pathList->itemCount; index++)
{
if(player->currentArea->areaExits[index] != NULL)
if (strncmp(getFromList(player->currentArea->pathList, index)->path->pathName,
requestedPath, 32) == 0)
{
if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0)
{
printf("%s: %s\n", player->playerName, player->currentArea->areaExits[index]->pathName);
player->currentArea = player->currentArea->areaExits[index]->areaToJoin;
return 0;
}
}
}
printf("%s: %s\n", player->playerName, getFromList(player->currentArea->pathList, index)->path->pathName);
player->currentArea = getFromList(player->currentArea->pathList, index)->path->areaToJoin;
return 0;
}
}
return 1;
}

View File

@ -1,34 +1,18 @@
// gamelogic.h: Header file contatning function prototypes and datastructures
// for dealing with the game's logic.
// gamelogic.h: Function prototypes and data-structures for dealing with game logic.
// Barry Kane, 2022.
#ifndef GAMELOGIC_H
#define GAMELOGIC_H
#include "queue.h"
#include "areadata.h"
#include "constants.h"
#include "playerdata.h"
#include "inputoutput.h"
// =======================
// -=[ Main Game Loop ]=-:
// =======================
// ========================
// -=[ Data Structures ]=-:
// ========================
// A datastructure containing the needed parameters for a main game loop:
typedef struct gameLogicParameters
{
int * playerCount;
areaNode * areaList;
playerInfo * connectedPlayers;
inputMessageQueue * inputQueue;
outputMessageQueue * outputQueue;
skillList * globalSkillList;
} gameLogicParameters;
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicLoop(void * parameters);
// ======================
// -=[ Command Queue ]=-:
// ======================
// An event for storing the information needed to evaluate a command:
typedef struct commandEvent commandEvent;
typedef struct commandEvent
{
@ -38,45 +22,44 @@ typedef struct commandEvent
char * arguments;
} commandEvent;
// A first-in first-out queue for message input from players:
typedef struct commandQueue
// A data-structure containing the needed parameters for the main game loop:
typedef struct gameLogicParameters
{
bool lock;
bool paused;
int currentLength;
commandEvent * back;
commandEvent * front;
} commandQueue;
// Players:
int * playerCount;
playerInfo * connectedPlayers;
// Create a commandQueue:
commandQueue * createCommandQueue(void);
// Queues:
queue * inputQueue;
queue * outputQueue;
// Enqueue a command to a commandQueue:
int queueCommand(commandQueue * queue, char * command, char * arguments,
int commandLength, int argumentsLength , playerInfo * callingPlayer);
// Lists:
list * areaList;
list * globalSkillList;
} gameLogicParameters;
// Enqueue a messaged command to a commandQueue:
int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue);
// ========================
// -=[ Functions ]=-:
// ========================
// Dequeue the front commandEvent from a commandQueue:
int dequeueCommand(commandQueue * queue);
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicHandler(void * parameters);
// Return the front commandEvent from a commandQueue:
commandEvent * peekCommand(commandQueue * queue);
// 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:
int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue);
// Evaluate the next commandEvent in a queue:
int evaluateNextCommand(gameLogicParameters * parameters, queue * queue);
/* // Evaluate the given commandEvent: */
/* int evaluateCommand(gameLogicParameters * parameters, commandEvent * command); */
// Enqueue a command to a queue:
void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength,
playerInfo * callingPlayer);
// ============================
// -=[ Gameplay Primitives ]=-:
// ============================
// Player movement:
int movePlayerToArea(playerInfo * player, char * requestedPath);
// The possible outcomes of a check or challenge:
typedef enum outcome
{
CRITICAL_FAILURE,
@ -86,10 +69,13 @@ typedef enum outcome
ERROR
} outcome;
// Run a stat check:
// 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:
outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList);
// 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

View File

@ -5,7 +5,10 @@
#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"
@ -14,11 +17,14 @@
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,
@ -32,11 +38,14 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend)
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,
@ -46,282 +55,95 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM
return returnValue;
}
outputMessageQueue * createOutputMessageQueue(void)
// Allocate and initialize an outputMessage targeted to a variable amount of players:
outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount)
{
outputMessageQueue * newQueue = malloc(sizeof(outputMessageQueue));
newQueue->front = NULL;
newQueue->back = NULL;
newQueue->currentLength = 0;
newQueue->lock = false;
return newQueue;
}
int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue)
{
// Copy the message into a new output message:
// Allocate a new output message:
outputMessage * newOutputMessage = malloc(sizeof(outputMessage));
// Allocate the internal userMessage to store the message:
newOutputMessage->content = malloc(sizeof(userMessage));
// Copy the userMessage to the internal userMessage:
strncpy(newOutputMessage->content->senderName, messageToQueue.senderName, 32);
strncpy(newOutputMessage->content->messageContent, messageToQueue.messageContent, MAX);
// We have no targets, NULL sends to all players in an area:
newOutputMessage->targets[0] = NULL;
// Wait for the queue to unlock:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check that we're not overflowing the queue:
if ((queue->currentLength + 1) > MAXQUEUELENGTH)
{
// Unlock the queue:
queue->lock = false;
return -1;
}
else
{
// If the queue is empty, set the first message as both the front and back of the queue:
if(queue->front == NULL)
{
queue->front = newOutputMessage;
queue->back = newOutputMessage;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
else
{
queue->back->next = newOutputMessage;
queue->back = newOutputMessage;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
}
}
int queueTargetedOutputMessage(outputMessageQueue * queue,
userMessage * messageToQueue, playerInfo ** targets, int numberOfTargets)
{
// Copy the message into a new output message:
outputMessage * newOutputMessage = malloc(sizeof(outputMessage));
// Allocate the internal userMessage to store the message:
newOutputMessage->content = malloc(sizeof(userMessage));
// Set the appropriate recipients:
for(int index = 0; index < numberOfTargets && index < PLAYERCOUNT; index++)
{
newOutputMessage->targets[index] = targets[index];
}
for(int index = numberOfTargets; index < PLAYERCOUNT; index++)
{
newOutputMessage->targets[index] = NULL;
}
// 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;
// Copy the userMessage to the internal userMessage:
strncpy(newOutputMessage->content->senderName, messageToQueue->senderName, 32);
strncpy(newOutputMessage->content->messageContent, messageToQueue->messageContent, MAX);
while (true)
{
// If there's nothing to do, put the thread to sleep:
if (outputQueue->itemCount == 0)
{
pthread_cond_wait(&outputQueue->condition, &outputQueue->mutex);
}
// Wait for the queue to unlock:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check that we're not overflowing the queue:
if ((queue->currentLength + 1) > MAXQUEUELENGTH)
{
// Unlock the queue:
queue->lock = false;
return -1;
}
else
{
// If the queue is empty, set the first message as both the front and back of the queue:
if(queue->front == NULL)
// Run through the output queue and send all unsent messages:
while (outputQueue->itemCount != 0)
{
queue->front = newOutputMessage;
queue->back = newOutputMessage;
queue->currentLength++;
// 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:
queue->lock = false;
return 0;
}
else
{
queue->back->next = newOutputMessage;
queue->back = newOutputMessage;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
}
}
int dequeueOutputMessage(outputMessageQueue * queue)
{
// Wait for the queue to unlock:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check the list isn't empty:
if(queue->front == NULL)
{
queue->lock = false;
return -1;
}
// If there is only one item in the queue:
else if(queue->front == queue->back)
{
free(queue->front->content);
free(queue->front);
queue->front = NULL;
queue->back = NULL;
queue->currentLength--;
queue->lock = false;
return 0;
}
// Remove the front item:
else
{
outputMessage * messageToDelete = queue->front;
queue->front = queue->front->next;
free(messageToDelete->content);
free(messageToDelete);
queue->currentLength--;
queue->lock = false;
return 0;
}
}
inputMessageQueue * createInputMessageQueue(void)
{
inputMessageQueue * newQueue = malloc(sizeof(inputMessageQueue));
newQueue->front = NULL;
newQueue->back = NULL;
newQueue->currentLength = 0;
newQueue->lock = false;
return newQueue;
}
int dequeueInputMessage(inputMessageQueue * queue)
{
// Wait for the queue to unlock:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check the list isn't empty:
if(queue->front == NULL)
{
queue->lock = false;
return -1;
}
// If there is only one item in the queue:
else if(queue->front == queue->back)
{
free(queue->front->content);
free(queue->front);
queue->front = NULL;
queue->back = NULL;
queue->currentLength--;
queue->lock = false;
return 0;
}
// Remove the front item:
else
{
inputMessage * messageToDelete = queue->front;
queue->front = queue->front->next;
free(messageToDelete->content);
free(messageToDelete);
queue->currentLength--;
queue->lock = false;
return 0;
}
}
int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer)
{
// Copy the message into a new input message:
inputMessage * inputMessage = malloc(sizeof(inputMessage));
// Allocate the internal userMessage to store the message:
inputMessage->content = malloc(sizeof(userMessage));
// Copy the userMessage to the internal userMessage:
strncpy(inputMessage->content->senderName, messageToQueue.senderName, 32);
strncpy(inputMessage->content->messageContent, messageToQueue.messageContent, MAX);
// We have no targets, NULL sends to all players in an area:
inputMessage->sender = sendingPlayer;
// Wait for the queue to unlock:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check that we're not overflowing the queue:
if ((queue->currentLength + 1) > MAXQUEUELENGTH)
{
// Unlock the queue:
queue->lock = false;
return -1;
}
else
{
// If the queue is empty, set the first message as both the front and back of the queue:
if(queue->front == NULL)
{
queue->front = inputMessage;
queue->back = inputMessage;
queue->currentLength++;
outputQueue->lock = false;
// Unlock the queue:
queue->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);
}
}
return 0;
// 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);
}
else
{
queue->back->next = inputMessage;
queue->back = inputMessage;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
}
}
}
// Sanatize user input to ensure it's okay to process:
void userInputSanatize(char * inputString, int length)
{
for(int index = 0; index <= length; index++)
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';
@ -329,29 +151,23 @@ void userInputSanatize(char * inputString, int length)
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';
}
// Return the front inputMessage from an inputMessageQueue:
inputMessage * peekInputMessage(inputMessageQueue * queue)
{
return queue->front;
}
outputMessage * peekOutputMessage(outputMessageQueue * queue)
{
return queue->front;
}

View File

@ -4,22 +4,54 @@
#ifndef INPUTOUTPUT_H
#define INPUTOUTPUT_H
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include "constants.h"
#include "playerdata.h"
#include <stdbool.h>
#include <gnutls/gnutls.h>
// A message datastructure containing a user/character name and the content:
#include "queue.h"
#include "constants.h"
#include "playerdata.h"
// Let the compiler know there will be structs defined elsewhere:
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;
// ==================
// -=[Message I/O]=-:
// ==================
// 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);
@ -27,80 +59,16 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend);
// Receives a message from a given TLS session, wraps the calls to gnutls_read:
int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage);
// ===================
// -=[Output Queue]=-:
// ===================
typedef struct outputMessage outputMessage;
typedef struct outputMessage
{
outputMessage * next;
playerInfo * targets[PLAYERCOUNT];
userMessage * content;
} outputMessage;
// Create a targetedOutput message to be delivered to the players pointed to in recipients:
outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount);
// A first-in first-out queue for message output to players:
typedef struct outputMessageQueue
{
bool lock;
int currentLength;
outputMessage * back;
outputMessage * front;
} outputMessageQueue;
// A function for the output thread, which sends queued messages:
void * outputThreadHandler(void * parameters);
// Creates and initializes a outputMessageQueue:
outputMessageQueue * createOutputMessageQueue(void);
// Enqueue a userMessage to an outputMessageQueue:
int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue);
int queueTargetedOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue,
playerInfo ** targets, int numberOfTargets);
// Dequeue the front outputMessage from an outputMessageQueue:
int dequeueOutputMessage(outputMessageQueue * queue);
// Return the front outputMessage from an outputMessageQueue:
outputMessage * peekOutputMessage(outputMessageQueue * queue);
// ==================
// -=[Input Queue]=-:
// ==================
typedef struct inputMessage inputMessage;
typedef struct inputMessage
{
inputMessage * next;
playerInfo * sender;
userMessage * content;
} inputMessage;
// A first-in first-out queue for message input from players:
typedef struct inputMessageQueue
{
bool lock;
int currentLength;
inputMessage * back;
inputMessage * front;
} inputMessageQueue;
// Create a inputMessageQueue:
inputMessageQueue * createInputMessageQueue(void);
// Enqueue a userMessage to an inputMessageQueue:
int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer);
// Dequeue the front inputMessage from an inputMessageQueue:
int dequeueInputMessage(inputMessageQueue * queue);
// Return the front inputMessage from an inputMessageQueue:
inputMessage * peekInputMessage(inputMessageQueue * queue);
// =======================
// -=[Input Sanitation]=-:
// =======================
// Sanatize user input to ensure it's okay to send to the server:
// Sanatize user input to ensure it's okay to process:
void userInputSanatize(char * inputString, int length);
// Sanatize user names so they display correctly;
// Sanatize user names so they display correctly:
void userNameSanatize(char * inputString, int length);
#endif

479
src/linkedlist.c Normal file
View File

@ -0,0 +1,479 @@
// 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;
}
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;
}

85
src/linkedlist.h Normal file
View File

@ -0,0 +1,85 @@
// linkedlist.h: Defines the linked list datatype for SilverMUD.
// Barry Kane, 2022.
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
#include "areadata.h"
#include "playerdata.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

View File

@ -9,12 +9,12 @@
#include "playerdata.h"
// Create a new skill and add it to the global skill list:
int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill)
listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill)
{
if(skillNameLength >= 32)
if (skillNameLength >= 32)
{
fprintf(stderr, "Skill name is too long. Please shorten the name and try again.\n");
return -1;
return NULL;
}
playerSkill * newSkill = malloc(sizeof(playerSkill));
@ -26,109 +26,56 @@ int createSkill(skillList * globalSkillList, char * skillName, int skillNameLeng
newSkill->trainedSkill = trainedSkill;
// Add the skill to a node in the list:
return(addSkillNode(globalSkillList, newSkill));
}
// Add a skill node to a skill list:
int addSkillNode(skillList * skillList, playerSkill * skill)
{
if(skillList->head == NULL)
{
skillList->head = malloc(sizeof(skillNode));
skillList->head->skill = skill;
skillList->head->next = NULL;
skillList->skillCount = 1;
return 0;
}
else
{
skillNode * currentNode = skillList->head;
while(currentNode->next != NULL)
{
currentNode = currentNode->next;
}
currentNode->next = malloc(sizeof(skillNode));
currentNode->next->skill = skill;
currentNode->next->next = NULL;
skillList->skillCount++;
return skillList->skillCount;
}
}
// Remove a skill node from a skill list:
int removeSkillNode(skillList * skillList, playerSkill * skill)
{
if(skillList->head->skill == skill)
{
free(skillList->head->skill);
if(skillList->head->next != NULL)
{
skillNode * deletedNode = skillList->head;
skillList->head = skillList->head->next;
free(deletedNode);
return 0;
}
else
{
skillNode * currentNode = skillList->head;
skillNode * previousNode = skillList->head;
while(currentNode->skill != skill)
{
if(currentNode->next == NULL)
{
return -1;
}
previousNode = currentNode;
currentNode = currentNode->next;
}
free(currentNode->skill);
previousNode->next = currentNode->next;
free(currentNode);
return 0;
}
}
return -1;
return(addToList(globalSkillList, newSkill, SKILL));
}
// Take a skill and add it to the player's skill list:
int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer)
int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer)
{
skillNode * currentNode = globalSkillList->head;
while(strncmp(skillName, currentNode->skill->skillName, 32) != 0)
// Check if the skill exists in the game:
size_t globalIndex = 0;
bool skillExists = false;
while (globalIndex < globalSkillList->itemCount)
{
if(currentNode->next == NULL)
if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) == 0)
{
fprintf(stderr, "Skill doesn't exist in skill list.\n");
return -1;
skillExists = true;
break;
}
currentNode = currentNode->next;
globalIndex++;
}
bool playerHasSkill = false;
skillNode * currentPlayerNode = targetPlayer->skills->head;
while(currentPlayerNode != NULL)
if (!skillExists)
{
if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0)
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;
}
currentPlayerNode = currentPlayerNode->next;
playerIndex++;
}
if(playerHasSkill)
if (playerHasSkill)
{
currentPlayerNode->skill->skillPoints++;
getFromList(targetPlayer->skills, playerIndex)->skill->skillPoints++;
}
// Copy the skill into the player's skill list:
else
{
addSkillNode(targetPlayer->skills, currentNode->skill);
currentPlayerNode = targetPlayer->skills->head;
while(currentPlayerNode->next != NULL)
{
currentPlayerNode = currentPlayerNode->next;
}
currentPlayerNode->skill->skillPoints = 1;
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;
}
@ -138,7 +85,7 @@ int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength
coreStat getCoreStatFromString(char * inputString, int stringLength)
{
// Check we've got a long enough string to fit a stat:
if(stringLength < 4)
if (stringLength < 4)
{
return INVALID;
}
@ -152,11 +99,11 @@ coreStat getCoreStatFromString(char * inputString, int stringLength)
// 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 < 9)
{
if(stringLength <= 4)
if (stringLength <= 4)
{
if(strncmp(string, "wits", 4) == 0)
if (strncmp(string, "wits", 4) == 0)
{
free(string);
return WITS;
@ -168,19 +115,19 @@ coreStat getCoreStatFromString(char * inputString, int stringLength)
}
}
// Hopefully one of the seven letter long ones:
else if(stringLength <= 7)
else if (stringLength <= 7)
{
if(strncmp(string, "strength", 7) == 0)
if (strncmp(string, "strength", 7) == 0)
{
free(string);
return STRENGTH;
}
else if(strncmp(string, "dexerity", 7) == 0)
else if (strncmp(string, "dexerity", 7) == 0)
{
free(string);
return DEXERITY;
}
if(strncmp(string, "wits", 4) == 0)
if (strncmp(string, "wits", 4) == 0)
{
free(string);
return WITS;
@ -194,27 +141,27 @@ coreStat getCoreStatFromString(char * inputString, int stringLength)
// Hopefully one of the 8 letter long stats:
else
{
if(strncmp(string, "intellect", 8) == 0)
if (strncmp(string, "intellect", 8) == 0)
{
free(string);
return INTELLECT;
}
else if(strncmp(string, "endurance", 8) == 0)
else if (strncmp(string, "endurance", 8) == 0)
{
free(string);
return ENDURANCE;
}
else if(strncmp(string, "strength", 7) == 0)
else if (strncmp(string, "strength", 7) == 0)
{
free(string);
return STRENGTH;
}
else if(strncmp(string, "dexerity", 7) == 0)
else if (strncmp(string, "dexerity", 7) == 0)
{
free(string);
return DEXERITY;
}
if(strncmp(string, "wits", 4) == 0)
if (strncmp(string, "wits", 4) == 0)
{
free(string);
return WITS;
@ -229,27 +176,27 @@ coreStat getCoreStatFromString(char * inputString, int stringLength)
// Worst case, it's definitely a dirty string, compare them all:
else
{
if(strncmp(string, "wits", 4) == 0)
if (strncmp(string, "wits", 4) == 0)
{
free(string);
return WITS;
}
else if(strncmp(string, "intellect", 8) == 0)
else if (strncmp(string, "intellect", 8) == 0)
{
free(string);
return INTELLECT;
}
else if(strncmp(string, "strength", 7) == 0)
else if (strncmp(string, "strength", 7) == 0)
{
free(string);
return STRENGTH;
}
else if(strncmp(string, "endurance", 8) == 0)
else if (strncmp(string, "endurance", 8) == 0)
{
free(string);
return ENDURANCE;
}
else if(strncmp(string, "dexerity", 7) == 0)
else if (strncmp(string, "dexerity", 7) == 0)
{
free(string);
return DEXERITY;
@ -262,28 +209,11 @@ coreStat getCoreStatFromString(char * inputString, int stringLength)
}
}
// Deallocate a player's information including the skill lists and stats:
int deallocatePlayer(playerInfo * playerToDeallocate)
{
// Deallocate the skill list:
if(playerToDeallocate->skills->skillCount > 0)
{
// Allocate enough pointers:
skillNode * nodesToDeallocate[playerToDeallocate->skills->skillCount];
skillNode * currentSkillNode = playerToDeallocate->skills->head;
// Get a list of all the nodes together:
for(int index = 0; index < playerToDeallocate->skills->skillCount; index++)
{
nodesToDeallocate[index] = currentSkillNode;
currentSkillNode = currentSkillNode->next;
}
// Deallocate all the nodes:
for(int index = 0; index < playerToDeallocate->skills->skillCount; index++)
{
free(nodesToDeallocate[index]);
}
}
destroyList(&(playerToDeallocate->skills));
// Deallocate the stat block:
free(playerToDeallocate->stats);

View File

@ -6,7 +6,15 @@
#include <stdbool.h>
#include "areadata.h"
#include "constants.h"
#include "linkedlist.h"
// Let the compiler know there will be structs defined elsewhere:
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:
@ -29,6 +37,7 @@ typedef struct statBlock
int skillPoints;
} statBlock;
// Information about a skill, including skill levels and modifiers for the player:
typedef struct playerSkill
{
char skillName[32];
@ -37,27 +46,16 @@ typedef struct playerSkill
bool trainedSkill;
} playerSkill;
typedef struct skillNode skillNode;
struct skillNode
{
playerSkill * skill;
skillNode * next;
};
typedef struct skillList
{
skillNode * head;
int skillCount;
} skillList;
\
// Information about a single player's character:
typedef struct playerInfo
{
char playerName[32];
playerArea * currentArea;
statBlock * stats;
skillList * skills;
list * skills;
} playerInfo;
// An enum of the main stats of the game:
typedef enum coreStat
{
WITS,
@ -69,23 +67,16 @@ typedef enum coreStat
} coreStat;
// Create a new skill and add it to the global skill list:
int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill);
// Add a skill node to a skill list:
int addSkillNode(skillList * skillList, playerSkill * skill);
// Remove a skill node from a skill list:
int removeSkillNode(skillList * skillList, playerSkill * skill);
int removeSkillByID(skillList * skillList, playerSkill * skill);
listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill);
// Take a skill and add it to the player's skill list:
int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer);
int takeSkillbyID(skillList * globalSkillList, int skillID, playerInfo * targetPlayer);
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:
// Deallocate a player's information including the skill lists and stats:
int deallocatePlayer(playerInfo * playerToDeallocate);
#endif

220
src/queue.c Normal file
View File

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

71
src/queue.h Normal file
View File

@ -0,0 +1,71 @@
// queue.h: Defines the queue data type and associated function prototypes for SilverMUD.
// Barry Kane, 2022
#ifndef QUEUE_H
#define QUEUE_H
#include "gamelogic.h"
#include "inputoutput.h"
// ========================
// -=[ Data Structures ]=-:
// ========================
// Let the compiler know there will be structs defined elsewhere:
typedef struct queue queue;
// 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

View File

@ -1,4 +1,4 @@
// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.4.
// 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>
@ -17,59 +17,84 @@
#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"
typedef struct sockaddr sockaddr;
void sigintHandler(int signal)
{
exit(EXIT_SUCCESS);
printf("Caught signal %d.\n", signal);
_exit(EXIT_SUCCESS);
}
int main(int argc, char ** argv)
{
time_t currentTime;
unsigned delay = 4000;
int socketFileDesc, connectionFileDesc, length, clientsAmount,
socketCheck, activityCheck, returnVal;
fd_set connectedClients;
pthread_t gameLogicThread;
pthread_t gameLogicThread, outputThread;
int clientSockets[PLAYERCOUNT];
userMessage sendBuffer, receiveBuffer;
playerInfo connectedPlayers[PLAYERCOUNT];
char testString[32] = "Hehe.";
struct sockaddr_in serverAddress, clientAddress;
inputMessageQueue * inputQueue = createInputMessageQueue();
outputMessageQueue * outputQueue = createOutputMessageQueue();
queue * inputQueue = createQueue(), * outputQueue = createQueue();
// Parse command-line options:
int currentopt = 0;
while ((currentopt = getopt(argc, argv, "d:")) != -1)
{
switch(currentopt)
{
case 'd':
{
delay = atoi(optarg);
break;
}
}
}
// Set the handler for SIGINT:
signal(2, sigintHandler);
// -==[ TEST GAME-STATE INITIALIZATION ]==-
// Initialize test areas:
areaNode * areas = createAreaList(createArea("Login Area", "Please login with the /join command."));
addAreaNodeToList(areas, createArea("Temple Entrance",
"You are standing outside a large, elaborate temple, of white marble and delicate construction. "
"Etched onto the left pillar next to the large opening is the same symbol, over and over again, a gentle curve with it's ends pointing to the right. "
"A similar symbol is on the right pillar, but it's ends are pointing to the left. "));
addAreaNodeToList(areas, createArea("The Hall of Documentation",
"Just past the threshold of the entrance lies a large hall, with bookshelves lining the walls, ceiling to floor. "
"The shelves are filled to the brim with finely-bound books, each with titles in silver lettering on the spine. "
"There are countless books, but you notice a large lectern in the center of the room, and a path leading upwards at the back. "));
addAreaNodeToList(areas, createArea("Monument to GNU",
"A beautifully ornate statue of GNU is above you on a pedestal. "
"Inscribed into the pillar, over and over, is the phrase \"M-x exalt\", in delicate gold letters. "
"You can't help but be awestruck."));
list * areas = createList(AREA);
addToList(areas, createArea("Login Area", "Please login with the /join command."), 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(getAreaFromList(areas, 1), getAreaFromList(areas, 2), "Go inside the temple.", "Leave the temple.");
createPath(getAreaFromList(areas, 3), getAreaFromList(areas, 2), "Back to the Hall of Documentation.", "Path to Enlightenment.");
skillList * globalSkillList = malloc(sizeof(skillList));
globalSkillList->head = NULL;
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);
@ -86,19 +111,18 @@ int main(int argc, char ** argv)
// OH NO IT'S NOT MEMORY SAFE BETTER REWRITE IT IN RUST
// But wait, we know the string won't be too big, so it's fine.
strcpy(connectedPlayers[index].playerName, testString);
connectedPlayers[index].currentArea = getAreaFromList(areas, 0);
connectedPlayers[index].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 = calloc(1, sizeof(skillList));
connectedPlayers[index].skills->head = NULL;
connectedPlayers[index].skills = createList(SKILL);
}
// -==[ TEST GAME-STATE INITIALIZATION END ]==-
// Give an intro: Display the Silverkin Industries logo and splash text.
slowPrint(logostring, 3000);
slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.4\n", 5000);
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:
srandom((unsigned)time(&currentTime));
@ -119,10 +143,9 @@ int main(int argc, char ** argv)
else
{
slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000);
slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay);
}
//
bzero(&serverAddress, sizeof(serverAddress));
// Assign IP and port:
@ -139,7 +162,7 @@ int main(int argc, char ** argv)
else
{
slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", 5000);
slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", delay);
}
// Let's start listening:
@ -150,7 +173,7 @@ int main(int argc, char ** argv)
}
else
{
slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", 5000);
slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", delay);
}
length = sizeof(clientAddress);
@ -173,7 +196,7 @@ int main(int argc, char ** argv)
gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey);
gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
}
slowPrint("\tTLS Preparation is:\t\033[32;40mGREEN.\033[0m\n", 5000);
slowPrint("\tTLS Preparation is:\t\033[32;40mGREEN.\033[0m\n", delay);
// Prepare the game logic thread:
gameLogicParameters * gameLogicThreadParameters = malloc(sizeof(gameLogicParameters));
@ -183,15 +206,23 @@ int main(int argc, char ** argv)
gameLogicThreadParameters->outputQueue = outputQueue;
gameLogicThreadParameters->inputQueue = inputQueue;
gameLogicThreadParameters->areaList = areas;
pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters);
pthread_create(&gameLogicThread, NULL, &gameLogicHandler, gameLogicThreadParameters);
slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", 5000);
slowPrint("=====\n", 5000);
struct timeval timeout = {0, 500};
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);
slowPrint("=====\n", delay);
while(true)
{
// Clear the set of file descriptors angad add the master socket:
// Clear the set of file descriptors and add the master socket:
FD_ZERO(&connectedClients);
FD_SET(socketFileDesc, &connectedClients);
clientsAmount = socketFileDesc;
@ -215,7 +246,7 @@ int main(int argc, char ** argv)
}
// See if a connection is ready to be interacted with:
activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout);
activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL);
// Check if select() worked:
if ((activityCheck < 0) && (errno != EINTR))
@ -251,7 +282,17 @@ int main(int argc, char ** argv)
strcpy(sendBuffer.messageContent, "Welcome to the server!");
messageSend(tlssessions[index], &sendBuffer);
strcpy(receiveBuffer.messageContent, "/look");
queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]);
// 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;
}
}
@ -280,7 +321,7 @@ int main(int argc, char ** argv)
// Clear out the old player state so that a new one may join:
sprintf(testString, "UNNAMED %d", index);
strcpy(connectedPlayers[index].playerName, testString);
connectedPlayers[index].currentArea = getAreaFromList(areas, 0);
connectedPlayers[index].currentArea = getFromList(areas, 0)->area;
// Prepare a fresh SSL session for the next new player:
gnutls_init(&tlssessions[index], GNUTLS_SERVER);
@ -291,48 +332,22 @@ int main(int argc, char ** argv)
// Otherwise, they've sent a message:
else
{
queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]);
// 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);
}
}
}
}
// Run through the output queue and send all unused messages:
while(outputQueue->currentLength != 0)
{
while(outputQueue->lock);
outputQueue->lock = true;
outputMessage * message = peekOutputMessage(outputQueue);
outputQueue->lock = false;
// If the first target is set to NULL, it's intended for all connected:
if(message->targets[0] == NULL)
{
for (int index = 0; index < PLAYERCOUNT; index++)
{
messageSend(tlssessions[index], message->content);
}
}
else
{
int targetIndex = 0;
for(int index = 0; index < PLAYERCOUNT; index++)
{
if(message->targets[targetIndex] == NULL)
{
break;
}
if(&connectedPlayers[index] == message->targets[targetIndex])
{
targetIndex++;
messageSend(tlssessions[index], message->content);
}
}
}
dequeueOutputMessage(outputQueue);
}
}
pthread_cancel(gameLogicThread);
pthread_cancel(outputThread);
exit(EXIT_SUCCESS);
}

View File

@ -5,10 +5,11 @@
#include <unistd.h>
#include <ncurses.h>
void slowPrint(char * stringToPrint, int delay)
// 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')
while (stringToPrint[characterIndex] != '\0')
{
putchar(stringToPrint[characterIndex]);
// Flush the buffer so there's no line buffering.
@ -18,14 +19,15 @@ void slowPrint(char * stringToPrint, int delay)
}
}
void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded)
// The same, altered to work with ncurses:
void slowPrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded)
{
int characterIndex = 0;
if(bolded)
if (bolded)
{
wattron(window, A_BOLD);
}
while(stringToPrint[characterIndex] != '\0')
while (stringToPrint[characterIndex] != '\0')
{
waddch(window, stringToPrint[characterIndex]);
// Refresh the ncurses screen.
@ -33,17 +35,18 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bol
usleep(delay);
characterIndex++;
}
if(bolded)
if (bolded)
{
wattroff(window, A_BOLD);
}
wrefresh(window);
}
void bruteforcePrint(char * stringToPrint, int delay)
// 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')
while (stringToPrint[characterIndex] != '\0')
{
for(unsigned char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++)
{
@ -58,14 +61,15 @@ void bruteforcePrint(char * stringToPrint, int delay)
}
}
void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded)
// The same, altered to work with ncurses:
void bruteforcePrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded)
{
int characterIndex = 0;
if(bolded)
if (bolded)
{
wattron(window, A_BOLD);
}
while(stringToPrint[characterIndex] != '\0')
while (stringToPrint[characterIndex] != '\0')
{
for(char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++)
{
@ -78,19 +82,20 @@ void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bo
waddch(window, stringToPrint[characterIndex]);
characterIndex++;
}
if(bolded)
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')
if (stringToWrap[index] == '\n')
{
characterCount = 0;
}
@ -98,13 +103,13 @@ void wrapString(char * stringToWrap, int stringLength, int screenWidth)
{
characterCount++;
}
if(characterCount == screenWidth)
if (characterCount == screenWidth)
{
while(!isspace(stringToWrap[index]) && index > 0)
while (!isspace(stringToWrap[index]) && index > 0)
{
index--;
}
if(index == 0)
if (index == 0)
{
return;
}

View File

@ -5,20 +5,23 @@
#include <stdio.h>
#include <ncurses.h>
// A character by character print, similar to a serial terminal with lower baud rate.
void slowPrint(char * stringToPrint, int delay);
// 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(char * stringToPrint, int delay, WINDOW * window, bool bolded);
// 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(char * stringToPrint, int delay);
// 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(char * stringToPrint, int delay, WINDOW * window, bool bolded);
// 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.
char * logostring =
const char * logostring =
" ///////\n"
" //////////////////////////////////////////\n"
" ///////////////////////////////////////////////////////////\n"
@ -32,5 +35,4 @@ char * logostring =
" # # # # # # # # ## # ### # # ## //\n"
" # # ### ##### ##### ### # # # # #### ### /\n";
void wrapString(char * stringToWrap, int stringLength, int screenWidth);
#endif

View File

@ -1,24 +1,46 @@
#include "../src/lists.h"
#include "../src/playerdata.h"
#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()
{
areaNode * areaList = createAreaList(createArea("Test Area A", "This is Test Area A"));
areaNode * counter = areaList;
addAreaNodeToList(areaList, createArea("Test Area B", "This is Test Area B"));
addAreaNodeToList(areaList, createArea("Test Area C", "This is Test Area C"));
for(int index = 0; index <= 2; index++)
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++)
{
printf("%s\n", counter->data->areaName);
counter = counter->next;
}
deleteAreaNodeFromList(areaList, getAreaFromList(areaList, 1));
addAreaNodeToList(areaList, createArea("Test Area D", "This is Test Area D"));
counter = areaList;
for(int index = 0; index <= 2; index++)
{
printf("%s\n", counter->data->areaName);
counter = counter->next;
sprintf(areaName, "Area %d", count);
sprintf(areaDescription, "This is Area %d.", count);
addToList(areaList, createArea(areaName, areaDescription) , AREA);
}
printAreaList(areaList);
printf("\n--==[ Inserting items into specific indexes. ]==--\n\n");
insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 0);
insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 6);
insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 11);
printAreaList(areaList);
printf("\n--==[ Removing certain areas from the list. ]==--\n\n");
removeFromList(areaList, AREA, 12);
removeFromList(areaList, AREA, 6);
removeFromList(areaList, AREA, 0);
printAreaList(areaList);
destroyList(&areaList);
printf("");
}

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

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