* Re-wrote the Server code again. Now much more reliable and passes all connections to the ConnectionManager properly.

* StandardCharacter re-wrote to support the new Server code.  Also added event method support for various states.
* ConnectionManager re-wrote to support the new server.
* Work on Log message importance started
* INetworked.Connect now requires a Socket as its parameter.
* StandardGame no longer has Properties for MaxConnections and MaxQueuedConnections.  This is handle via StandardGame.Start() parameters.
* CommandLogin command added.  Initial check-in and not fully implemented.
This commit is contained in:
Scionwest_cp 2012-03-03 11:17:01 -08:00
parent 5b141c62ed
commit 27f7e31772
12 changed files with 314 additions and 163 deletions

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Net.Sockets;
namespace MudEngine.Core.Interfaces namespace MudEngine.Core.Interfaces
{ {
@ -24,6 +25,6 @@ namespace MudEngine.Core.Interfaces
/// <summary> /// <summary>
/// Method for connecting a object to the server. /// Method for connecting a object to the server.
/// </summary> /// </summary>
void Connect(); void Connect(Socket connection);
} }
} }

View file

@ -21,7 +21,7 @@ namespace MudEngine.Core.Interfaces
/// <param name="path"></param> /// <param name="path"></param>
Boolean Save(String filename); Boolean Save(String filename);
Boolean Save(String filename, Boolean ignoreignoreFileWrite); Boolean Save(String filename, Boolean ignoreFileWrite);
/// <summary> /// <summary>
/// Load method for retrieving saved data from file. /// Load method for retrieving saved data from file.

View file

@ -11,6 +11,16 @@ namespace MudEngine.Core
/// </summary> /// </summary>
public static class Logger public static class Logger
{ {
public enum Importance
{
Critical = 0,
Error = 1,
Warning = 2,
Information = 3,
Chat = 4,
All = 5
}
/// <summary> /// <summary>
/// The Log Filename for the engine log. /// The Log Filename for the engine log.
/// </summary> /// </summary>
@ -42,6 +52,10 @@ namespace MudEngine.Core
_Messages.Clear(); _Messages.Clear();
} }
public static void WriteLine(String message, Importance importance)
{
}
/// <summary> /// <summary>
/// Writes a single line to the engine log file. /// Writes a single line to the engine log file.
/// </summary> /// </summary>

View file

@ -7,6 +7,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Xml.Linq; using System.Xml.Linq;
using System.Text.RegularExpressions;
using MudEngine.GameScripts; using MudEngine.GameScripts;
using MudEngine.Core.Interfaces; using MudEngine.Core.Interfaces;
@ -25,6 +26,8 @@ namespace MudEngine.Game.Characters
/// </summary> /// </summary>
public StandardGame Game { get; private set; } public StandardGame Game { get; private set; }
public string Filename { get; set; }
/// <summary> /// <summary>
/// Gets what this Characters role on the server is. /// Gets what this Characters role on the server is.
/// </summary> /// </summary>
@ -43,23 +46,27 @@ namespace MudEngine.Game.Characters
/// </summary> /// </summary>
public Boolean Immovable { get; set; } public Boolean Immovable { get; set; }
public Boolean Enabled { get; set; }
//TODO: Add current location to characters //TODO: Add current location to characters
//public IEnvironment CurrentLocation //public IEnvironment CurrentLocation
protected CommandSystem Commands { get; private set; } protected CommandSystem Commands { get; private set; }
public StandardCharacter(StandardGame game, String name, String description) : base(game, name, description) public StandardCharacter(StandardGame game, String name, String description)
: base(game, name, description)
{ {
this.Game = game; this.Game = game;
//Instance this Characters personal Command System with a copy of the command //Instance this Characters personal Command System with a copy of the command
//collection already loaded and prepared by the Active Game. //collection already loaded and prepared by the Active Game.
this.Commands = new CommandSystem(CommandSystem.Commands); this.Commands = new CommandSystem(CommandSystem.Commands);
this.OnConnectEvent += new OnConnectHandler(OnConnect); this.OnConnectEvent += new OnConnectHandler(OnConnect);
} }
public StandardCharacter(StandardGame game, String name, String description, Socket connection) : this(game, name, description) public StandardCharacter(StandardGame game, String name, String description, Socket connection)
: this(game, name, description)
{ {
this._Connection = connection; this._Connection = connection;
@ -82,23 +89,93 @@ namespace MudEngine.Game.Characters
return true; return true;
} }
internal void ExecuteCommand(string command)
{
//Process commands here.
if (this._InitialMessage)
{
command = this.CleanString(command);
this._InitialMessage = false;
}
this.Commands.Execute(command, this); public override void Load(string filename)
{
base.Load(filename);
} }
public void SendMessage(string message) public void Initialize()
{ {
lock (this) //throw new NotImplementedException();
this.Enabled = true;
}
public void Destroy()
{
throw new NotImplementedException();
}
internal void ExecuteCommand(string command)
{
if (this.Enabled)
{ {
_Writer.WriteLine(message); Commands.Execute(command, this);
SendMessage("");
SendMessage("Command:", false);
}
}
public void SendMessage(String data)
{
this.SendMessage(data, true);
}
public void SendMessage(String data, Boolean newLine)
{
try
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
if (newLine)
data += "\n\r";
this._Connection.Send(encoding.GetBytes(data));
}
catch (Exception)
{
Disconnect();
}
}
public String GetInput()
{
string input = String.Empty;
while (true)
{
try
{
byte[] buf = new byte[1];
Int32 recved = this._Connection.Receive(buf);
if (recved > 0)
{
if (buf[0] == '\n' && buffer.Count > 0)
{
if (buffer[buffer.Count - 1] == '\r')
buffer.RemoveAt(buffer.Count - 1);
System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
input = enc.GetString(buffer.ToArray());
buffer.Clear();
return input;
}
else
buffer.Add(buf[0]);
}
else if (recved == 0) //Disconnected
{
this.Enabled = false;
return "Disconnected.";
}
}
catch (Exception e)
{
//Flag as disabled
this.Enabled = false;
return e.Message;
}
} }
} }
@ -108,21 +185,16 @@ namespace MudEngine.Game.Characters
//Close our currently open socket. //Close our currently open socket.
this._Connection.Close(); this._Connection.Close();
//this._LoopThread.Abort();
//Remove this character from the Connection Manager //Remove this character from the Connection Manager
ConnectionManager.RemoveConnection(this); //ConnectionManager.RemoveConnection(this, );
Console.WriteLine("Disconnect Complete."); Console.WriteLine("Disconnect Complete.");
//Stop the Update() thread.
this._LoopThread.Abort();
} }
public void Connect() public void Connect(Socket connection)
{ {
_LoopThread = new Thread(Update); this._Connection = connection;
_LoopThread.Start();
this.SendMessage("");
OnConnectEvent(); OnConnectEvent();
} }
@ -133,8 +205,9 @@ namespace MudEngine.Game.Characters
while (this.Game.Enabled) while (this.Game.Enabled)
{ {
_Writer.Flush(); _Writer.Flush();
String line = _Reader.ReadLine(); //String line = CleanString(GetInput());
ExecuteCommand(line); //Console.WriteLine(line);
//ExecuteCommand(line);
} }
} }
catch catch
@ -146,8 +219,9 @@ namespace MudEngine.Game.Characters
} }
} }
String CleanString(string line) String CleanString(String line)
{ {
/*
if ((!String.IsNullOrEmpty(line)) && (line.Length > 0)) if ((!String.IsNullOrEmpty(line)) && (line.Length > 0))
{ {
System.Text.StringBuilder sb = new System.Text.StringBuilder(line.Length); System.Text.StringBuilder sb = new System.Text.StringBuilder(line.Length);
@ -162,46 +236,43 @@ namespace MudEngine.Game.Characters
} }
else else
return String.Empty; return String.Empty;
* */
Regex invalidChars = new Regex(
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
RegexOptions.Compiled);
if (String.IsNullOrEmpty(line))
return "";
else
return invalidChars.Replace(line, "");
} }
public delegate void OnConnectHandler(); public delegate void OnConnectHandler();
public event OnConnectHandler OnConnectEvent; public event OnConnectHandler OnConnectEvent;
public void OnConnect() public void OnConnect()
{ {
_Writer.WriteLine(this.Game.Server.MOTD); this.SendMessage(this.Game.Server.MOTD);
}
public delegate void OnDisconnectHandler();
public event OnDisconnectHandler OnDisconnectEvent;
public void OnDisconnect()
{
}
public delegate void OnLoginHandler();
public event OnLoginHandler OnLoginEvent;
public void OnLogin()
{
} }
private Socket _Connection; private Socket _Connection;
private Thread _LoopThread;
private StreamReader _Reader; private StreamReader _Reader;
private StreamWriter _Writer; private StreamWriter _Writer;
private Boolean _InitialMessage; private Boolean _InitialMessage;
private List<byte> buffer = new List<byte>();
public string Filename
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void Load(string filename)
{
throw new NotImplementedException();
}
public void Initialize()
{
throw new NotImplementedException();
}
public void Destroy()
{
throw new NotImplementedException();
}
} }
} }

View file

@ -23,10 +23,6 @@ namespace MudEngine.Game
public Boolean Multiplayer { get; set; } public Boolean Multiplayer { get; set; }
public Int32 MaximumPlayers { get; set; }
public Int32 MaxQueueSize { get; set; }
public Int32 MinimumPasswordSize { get; set; } public Int32 MinimumPasswordSize { get; set; }
public Boolean AutoSave { get; set; } public Boolean AutoSave { get; set; }
@ -49,18 +45,14 @@ namespace MudEngine.Game
this.Description = "A sample Mud game created using the Mud Designer kit."; this.Description = "A sample Mud game created using the Mud Designer kit.";
this.Version = "1.0"; this.Version = "1.0";
this.Multiplayer = true; this.Multiplayer = true;
this.MaximumPlayers = 50;
this.MinimumPasswordSize = 8; this.MinimumPasswordSize = 8;
this.MaxQueueSize = 20;
this.AutoSave = true; this.AutoSave = true;
//Setup our server. //Setup our server.
this.Server = new Server(port); this.Server = new Server(this, port);
this.Server.Port = port;
} }
public Boolean Start() public Boolean Start(Int32 maxPlayers, Int32 maxQueueSize)
{ {
Logger.WriteLine("Starting up Standard Game"); Logger.WriteLine("Starting up Standard Game");
@ -74,7 +66,7 @@ namespace MudEngine.Game
//Load World //Load World
//Start our server. //Start our server.
this.Server.Start(this); this.Server.Start(maxPlayers, maxQueueSize);
//If the server started without error, flag the Game as enabled. //If the server started without error, flag the Game as enabled.
if (this.Server.Enabled) if (this.Server.Enabled)

View file

@ -59,20 +59,20 @@ namespace MudEngine.GameScripts
return true; return true;
} }
public virtual Boolean Load(String filename) public virtual void Load(String filename)
{ {
try try
{ {
if (!File.Exists(filename)) if (!File.Exists(filename))
return false; return;
XElement data = XElement.Load(filename); XElement data = XElement.Load(filename);
} }
catch catch
{ {
return false; return;
} }
return true; return;
} }
public override string ToString() public override string ToString()

View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MudEngine.Core.Interface;
using MudEngine.Game;
using MudEngine.Game.Characters;
using MudEngine.Game.Environment;
using MudEngine.GameScripts;
namespace MudEngine.GameScripts.Commands
{
public class CommandLogin : ICommand
{
public string Name { get; set; }
public string Description { get; set; }
public List<string> Help { get; set; }
public CommandLogin()
{
Help = new List<string>();
Name = "Login";
Description = "Account login command.";
}
public void Execute(string command, Game.Characters.StandardCharacter character)
{
character.SendMessage("Please enter character name: ");
String name = String.Empty;
while (String.IsNullOrEmpty(name))
{
name = character.GetInput();
}
}
}
}

View file

@ -33,7 +33,7 @@ namespace MudEngine.GameScripts.Commands
//Loop through each character on the server and broadcast the message. //Loop through each character on the server and broadcast the message.
//TODO: This should only broadcast to characters that are in the same Environment. //TODO: This should only broadcast to characters that are in the same Environment.
foreach (StandardCharacter c in ConnectionManager.Connections) foreach (StandardCharacter c in character.Game.Server.ConnectionManager.GetConnectedCharacters())
{ {
//Only broadcast this message to those that are not the broadcastor. //Only broadcast this message to those that are not the broadcastor.
if (c != character) if (c != character)

View file

@ -12,54 +12,94 @@ using MudEngine.Game.Characters;
namespace MudEngine.Networking namespace MudEngine.Networking
{ {
public static class ConnectionManager public class ConnectionManager
{ {
//Collection of currently connected players. //Collection of currently connected players.
public static List<StandardCharacter> Connections { get; set; } internal List<Thread> _ConnectedThreads;
internal List<StandardCharacter> _ConnectedCharacters;
public ConnectionManager(Int32 maxPlayers)
{
this._ConnectedCharacters = new List<StandardCharacter>(maxPlayers);
this._ConnectedThreads = new List<Thread>(maxPlayers);
}
/// <summary> /// <summary>
/// Creates a new character for the player and sets it up on the server. /// Creates a new character for the player and sets it up on the server.
/// </summary> /// </summary>
/// <param name="game"></param> /// <param name="game"></param>
/// <param name="connection"></param> /// <param name="connection"></param>
public static void AddConnection(StandardGame game, Socket connection) public void AddConnection(StandardGame game, Socket connection)
{ {
//Exception checking.
if (Connections == null)
Connections = new List<StandardCharacter>();
//Instance a new character and provide it with the Socket. //Instance a new character and provide it with the Socket.
StandardCharacter character = new StandardCharacter(game, "New Player", "New networked client.", connection); StandardCharacter character = new StandardCharacter(game, "New Player", "New networked client.", connection);
//Add it to the Connections collection
Connections.Add(character);
//Invoke the Characters Server connection method //Invoke the Characters Server connection method
character.Connect(); character.Connect(connection);
this._ConnectedCharacters.Add(character);
this._ConnectedThreads.Add(new Thread(ReceiveDataThread));
Int32 index = this._ConnectedThreads.Count - 1;
this._ConnectedThreads[index].Start(index);
}
public StandardCharacter[] GetConnectedCharacters()
{
return this._ConnectedCharacters.ToArray();
}
private void ReceiveDataThread(Object index)
{
StandardCharacter character = this._ConnectedCharacters[(Int32)index];
character.Initialize();
while (character.Game.Server.Status == ServerStatus.Running &&
character.Enabled)
{
try
{
character.ExecuteCommand(character.GetInput());
}
catch
{
RemoveConnection(character);
}
}
RemoveConnection(character);
} }
/// <summary> /// <summary>
/// Removes the specified player character from the server. /// Removes the specified player character from the server.
/// </summary> /// </summary>
/// <param name="character"></param> /// <param name="character"></param>
public static void RemoveConnection(StandardCharacter character) public void RemoveConnection(StandardCharacter character)
{ {
Connections.Remove(character); character.Disconnect();
foreach (StandardCharacter c in this._ConnectedCharacters)
{
if (c.ID == character.ID)
{
Int32 index = _ConnectedCharacters.IndexOf(c);
this._ConnectedCharacters.Remove(character);
this._ConnectedThreads[index].Abort();
}
}
} }
/// <summary> /// <summary>
/// Disconnects all of the currently connected clients. /// Disconnects all of the currently connected clients.
/// </summary> /// </summary>
public static void DisconnectAll() public void DisconnectAll()
{ {
if (Connections == null) for (Int32 i = 0; i < this._ConnectedCharacters.Count; i++)
return;
foreach (StandardCharacter character in Connections)
{ {
character.Disconnect(); this._ConnectedCharacters[i].Disconnect();
this._ConnectedThreads[i].Abort();
} }
Connections.Clear();
this._ConnectedCharacters.Clear();
this._ConnectedThreads.Clear();
} }
} }
} }

View file

@ -14,103 +14,83 @@ using MudEngine.Game.Characters;
namespace MudEngine.Networking namespace MudEngine.Networking
{ {
public enum ServerStatus
{
Stopped = 0,
Starting = 1,
Running = 2,
}
[Category("Networking")] [Category("Networking")]
public class Server public class Server
{ {
[Category("Networking")] public ServerStatus Status { get; private set; }
[Description("The name of this server")]
public String Name { get; set; }
[Category("Networking")] public Int32 Port { get; private set; }
public Int32 Port { get; set; }
[Category("Networking")] public Int32 MaxConnections { get; private set; }
[Description("The Message Of The Day that is presented to users when they connect.")]
public String MOTD { get; set; }
[Category("Networking")] public Int32 MaxQueuedConnections { get; private set; }
[Description("Maximum number of people that can connect and play on this server at any time.")]
public Int32 MaxConnections { get; set; }
[Category("Networking")] public ConnectionManager ConnectionManager { get; private set; }
[Description("Maximum number of poeple that can be queued for connection.")]
public Int32 QueuedConnectionLimit { get; set; }
[Browsable(false)]
public Boolean Enabled { get; private set; } public Boolean Enabled { get; private set; }
/// <summary> public String MOTD { get; set; }
/// Sets up a server on the specified port.
/// </summary>
/// <param name="port"></param>
/// <param name="maxConnections"></param>
public Server(Int32 port)
{
Logger.WriteLine("Initializing Mud Server Settings");
this.Port = port;
this.MaxConnections = 50;
this.QueuedConnectionLimit = 20;
this.Name = "New Server";
this.MOTD = "Welcome to a sample Mud Engine game server!";
public Server(StandardGame game, Int32 port)
{
this.Port = port;
this.Status = ServerStatus.Stopped;
this.MaxConnections = 100;
this.MaxQueuedConnections = 10;
this._Game = game;
this._Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this._Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this._Server.Bind(new IPEndPoint(IPAddress.Any, this.Port));
} }
/// <summary> public void Start(Int32 maxConnections, Int32 maxQueueSize)
/// Starts the game server. It will listen on a different thread of incoming connections
/// </summary>
public void Start(StandardGame game)
{ {
//Start the server. if (this.Status != ServerStatus.Stopped)
Logger.WriteLine("Starting Mud Server."); return;
//Start listening for connections this.Status = ServerStatus.Starting;
this._Server.Listen(this.QueuedConnectionLimit);
//Launch the new connections listener on a new thread. this.MaxConnections = maxConnections;
this._ConnectionPool = new Thread(AcceptConnection); this.ConnectionManager = new ConnectionManager(this.MaxConnections);
this._ConnectionPool.Start();
//Flag the server as enabled. try
this.Enabled = true; {
IPEndPoint ip = new IPEndPoint(IPAddress.Any, this.Port);
this._Server.Bind(ip);
this._Server.Listen(this.MaxQueuedConnections);
//Save a reference to the currently active game this.Status = ServerStatus.Running;
this._Game = game; this.Enabled = true;
Logger.WriteLine("Server started."); this._ServerThread = new Thread(ServerLoop);
this._ServerThread.Start();
}
catch
{
this.Status = ServerStatus.Stopped;
}
} }
public void Stop() public void Stop()
{ {
//Flag the server as disabled.
this.Enabled = false;
//Stop the thread from listening for accepting new conections
this._ConnectionPool.Abort();
//Disconnect all of the currently connected clients.
ConnectionManager.DisconnectAll();
//Stop the server.
this._Server.Close();
} }
private void AcceptConnection() private void ServerLoop()
{ {
//While the server is enabled, constantly check for new connections. while (this.Status == ServerStatus.Running)
while (this.Enabled)
{ {
//Grab the new connection. this.ConnectionManager.AddConnection(this._Game, this._Server.Accept());
Socket incomingConnection = this._Server.Accept();
//Send it to the Connection Manager so a Character can be instanced.
ConnectionManager.AddConnection(this._Game, incomingConnection);
} }
} }
private Socket _Server;
private Thread _ConnectionPool;
private StandardGame _Game; private StandardGame _Game;
private Socket _Server;
private Thread _ServerThread;
} }
} }

View file

@ -54,6 +54,7 @@
<Compile Include="Core\ObjectCollection.cs" /> <Compile Include="Core\ObjectCollection.cs" />
<Compile Include="DAL\DataPaths.cs" /> <Compile Include="DAL\DataPaths.cs" />
<Compile Include="DAL\XMLData.cs" /> <Compile Include="DAL\XMLData.cs" />
<Compile Include="GameScripts\Commands\CommandLogin.cs" />
<Compile Include="GameScripts\Commands\CommandSay.cs" /> <Compile Include="GameScripts\Commands\CommandSay.cs" />
<Compile Include="Game\Characters\CharacterRoles.cs" /> <Compile Include="Game\Characters\CharacterRoles.cs" />
<Compile Include="Game\Characters\CharacterStats.cs" /> <Compile Include="Game\Characters\CharacterStats.cs" />

View file

@ -23,9 +23,19 @@ namespace WinPC_Server
Logger.Enabled = true; Logger.Enabled = true;
Logger.ConsoleOutPut = true; Logger.ConsoleOutPut = true;
//Instance and start the game. This will start the server. //Instance and setup our game
StandardGame game = new StandardGame("Sample Game"); StandardGame game = new StandardGame("Sample Game");
game.Start(); game.AutoSave = true;
game.Debugging = true;
game.Description = "This is a very simple game that was created to demonstrate MUD game creation with the Mud Designer Game Engine.";
game.HiddenRoomNames = false;
game.Multiplayer = true;
game.Server.MOTD = "Welcome to the Sample Game desmontratio server!";
game.Version = "1.0";
game.Website = "http://muddesigner.codeplex.com";
//Start the game and server.
game.Start(100, 20);
//Setup our Server console input class //Setup our Server console input class
ConsoleInput input = new ConsoleInput(); ConsoleInput input = new ConsoleInput();
@ -38,7 +48,7 @@ namespace WinPC_Server
while (game.Enabled) while (game.Enabled)
{ {
//Check the queued Console Input //Check the queued Console Input
if (input.Message.Equals("exit")) if (input.Message.ToLower().Equals("exit"))
{ {
//If the server console has a exit command entered. //If the server console has a exit command entered.
//stop the game. This will set game.Enabled to false. //stop the game. This will set game.Enabled to false.