MudEngine:

- FileManager.GetData() method now checks to ensure that the supplied filename exists prior to attempting to reading data.
 - Adjusted Game.AutoSaveInterval to 60 minutes instead of 1 minute.
 - Placed all of the File reading code within Game.Load() into a Try/Catch to prevent exceptions if there was an error restoring the game from saved state.
 - GameWorld.Load() now places saved Realms that were created at runtime into the Games Realm Collection. This allows for hard-coded and dynamically created Realms to co-exist.
 - BaseObject.Filename now appends a file extension to the filename if a filename is set but the user forgets to add the file extension. This affects all classes inheriting from BaseObject
 - BaseCharacter updated to invoke commands by instancing a copy of the command and executing it, instead of injecting commands from the player.
 - Added EditRealm command. Allows for editing existing Realms properties. Currently has editing of Simple Descriptions, Detailed Descriptions, name and filename fully implemented.
     Example: EditRealm MyRealmName

MudGame:
 - Removed Cali.Create() call. The MudGame no longer creates a hard-coded world. I want to get away from hard-coded environments all together. They should be built dynamically by admins.
This commit is contained in:
Scionwest_cp 2010-09-05 00:49:37 -07:00
parent abe11a5fd5
commit 9313611784
8 changed files with 610 additions and 32 deletions

View file

@ -0,0 +1,531 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using MudEngine.FileSystem;
using MudEngine.GameObjects;
using MudEngine.GameObjects.Characters;
using MudEngine.GameManagement;
using MudEngine.Commands;
using MudEngine.GameObjects.Environment;
namespace MudEngine.Commands
{
public class CommandEditRealm : IGameCommand
{
public Boolean Override { get; set; }
public String Name { get; set; }
public List<String> Help { get; set; }
private Realm realm;
BaseCharacter player;
public CommandEditRealm()
{
Help = new List<string>();
Help.Add("Use the Edit command to edit existing objects properties.");
Help.Add("Usage: Edit ObjectType ObjectName");
Help.Add("Usage: Edit ObjectType FullyQualifiedName");
Help.Add("Example: 'Edit Realm MyRealm'");
Help.Add("Example: 'Edit Room MyRealm>MyZone>Bedroom'");
}
public void Execute(String command, BaseCharacter player)
{
if ((player.Role == SecurityRoles.Admin) || (player.Role == SecurityRoles.GM))
{
//Get the admin-entered realm filename
String filename = command.Substring("EditRealm".Length).Trim().ToLower() + ".Realm";
//Raise the scope of the player reference to class level instead of method level.
this.player = player;
//Check if the filename field is empty, if so then nothing was provided by the admin
if (String.IsNullOrEmpty(filename))
{
player.Send("Realm Editing canceled. No Realm name was supplied.");
return;
}
//We have a filename, retrieve the Realm the admin wants to edit.
realm = player.ActiveGame.World.GetRealm(filename);
//If no Realm was retrieved (due to it not existing), let the admin know
//that the Realm filename was not valid.
if (realm == null)
{
player.Send("Realm Editing canceled. The supplied Realm name is not valid.");
return;
}
//Otherwise, the Realm does exist and was retrieved.
//Lets build our Editing menu's and allow for Realm Editing.
else
{
//Construct the main editing menu.
BuildMenuMain();
//Find out what menu option the admin wants to use.
Int32 value = 0;
//Attempt to convert the String entered by the admin into a numeric value
try
{
value = Convert.ToInt32(player.ReadInput());
}
//If a non-numeric value is supplied, the conversion failed. This is us catching that failure.
catch
{
player.Send("Realm Editing canceled. The supplied value was not numeric!");
return;
}
//Parse the menu option that the admin supplied.
ParseMenuSelection(value);
//let the admin know that we have now exited the editor.
player.Send("Editing completed.");
}
}
}
/// <summary>
/// Constructs the Main Editing menu for admins to look it.
/// </summary>
private void BuildMenuMain()
{
player.FlushConsole();
player.Send(Path.GetFileNameWithoutExtension(realm.Filename));
player.Send("Select from the available options below:");
player.Send("1: Descriptions");
player.Send("2: Names");
player.Send("3: Senses");
player.Send("4: Initial Realm");
player.Send("5: Exit");
player.Send("Enter numeric selection: ", false);
}
/// <summary>
/// Constructs the Descriptions Editing menu for admins to look at.
/// </summary>
private void BuildMenuDescriptions()
{
player.FlushConsole();
player.Send(Path.GetFileNameWithoutExtension(realm.Filename));
player.Send("Select from the available options below:");
player.Send("1: Simple Description");
player.Send("2: Detailed Descriptions");
player.Send("3: Exit");
player.Send("Enter a numeric selection: ", false);
}
/// <summary>
/// Constructs the Name Editing menu for admins to look at.
/// </summary>
private void BuildMenuNames()
{
player.FlushConsole();
player.Send(Path.GetFileNameWithoutExtension(realm.Filename));
player.Send("When you assign a Realm Name, the Filename is overwrote with your RealmName as a filename.");
player.Send("Example: RealmName of 'ExampleRealm' would automatically set a Filename of 'ExampleRealm.Realm'");
player.Send("");
player.Send("If you wish to have multiple Realms with the same visible name, you will need to specify a different Filename for each Realm.");
player.Send("Filenames are what you use when accessing objects as a Admin. Typically without the file extension.");
player.Send("Example: A Realm with a Visible name of \"My Test Realm\" can have a filename of \"Test.Realm\". You would access this object as a Admin by specifying a object name of \"Test\"");
player.Send("Select from the available options below:");
player.Send("");
player.Send("1: Realm Visibile Name");
player.Send("2: Realm Filename");
player.Send("3: Exit");
player.Send("Enter numeric selection: ", false);
}
/// <summary>
/// This method parses the Admin input based off the main editing menu
/// and sends the admin to what-ever sub-menu method we need to.
/// </summary>
/// <param name="value"></param>
private void ParseMenuSelection(Int32 value)
{
Int32 entry = 0;
switch (value)
{
case 1:
//User wants to edit the descriptions of this Realm.
//So lets build the menu and parse their menu selections.
BuildMenuDescriptions();
try
{
entry = Convert.ToInt32(player.ReadInput());
}
catch
{
player.Send("Realm Editing canceled. The supplied value was not numeric!");
return;
}
ParseDescriptionSelection(entry);
break;
case 2:
//User wants to edit the names of this Realm.
//So lets build the menu and parse their menu selections.
BuildMenuNames();
try
{
entry = Convert.ToInt32(player.ReadInput());
}
catch
{
player.Send("Realm Editing canceled. The supplied value was not numeric!");
return;
}
ParseNameSelection(entry);
break;
case 3:
//User wants to edit the Senses of this Realm.
break;
case 4:
//User wants to edit the Initial Realm settings of this Realm.
break;
case 5:
break;
default:
break;
}
}
/// <summary>
/// This method parses the admins menu selection from the Description menu
/// and adjusts the Realms descriptions as specified by the admin
/// </summary>
/// <param name="value"></param>
private void ParseDescriptionSelection(Int32 value)
{
switch (value)
{
//Simple Description
case 1:
player.FlushConsole();
player.Send("Enter a simple description for this Realm.");
player.Send("Simple Descriptions are single line only.");
player.Send("To create a blank description, you may simply press ENTER.");
player.Send("Entry: ", false);
//Read in the admins new simple description
realm.Description = player.ReadInput();
//Save the game world.
player.ActiveGame.Save();
player.Send("New Simple Description saved.");
break;
//Detailed Description
case 2:
Boolean isEditing = true;
Int32 line = 1;
//Loop until the admin is finished entering his/her multi-line description.
while (isEditing)
{
player.FlushConsole();
line = 1; //reset our line to the first line, so we can re-print the content to the admin.
//print some help info to the admin
player.Send("Enter a Detailed Description for this Realm.");
player.Send("Detailed Descriptions are multi-lined descriptions.");
player.Send("When you are finished entering a line, press ENTER to save it and move onto the next line.");
player.Send("When you are finished, you may type 'Exit' to save your changes and quit the Description editor.");
player.Send("To clear all detailed description lines, you may type 'Clear All'");
player.Send("To clear a single Detailed Description line, you may type 'Clear x', where x = line number to clear.");
player.Send("Example: 'Clear 2'");
player.Send("This will clear all contents from line number two in the detailed description.");
player.Send("If you make a mistake and need to edit a previously entered line, type 'Edit x' where x = line number you wish to edit.");
player.Send("An example would be 'Edit 3' which would allow you to change line #3.");
player.Send("");
player.Send("Detailed Description:");
//Loop through each description currently within the collection
//This will include lines created within this loop as well.
foreach (String desc in realm.DetailedDescription)
{
player.Send(line.ToString() + ": " + desc);
line++;
}
//Print the current line to the user and read their new description for that line.
player.Send(line.ToString() + ": ", false);
String input = player.ReadInput();
//Check what content the admin entered.
//If he/she typed 'exit' then we need to exit the editor.
if (input.ToLower() == "exit")
isEditing = false;
//If he/she typed 'edit' then we need to edit a supplied line number.
else if (input.ToLower().StartsWith("edit"))
{
//Retrieve the line number from the users input.
String editLine= input.Substring("edit".Length).Trim();
//If no line number was provided, cancel.
if (String.IsNullOrEmpty(editLine))
{
player.Send("Line editing canceled. You need to supply a line number.");
}
//Line number was provided, so lets skip to the specified line number
//and replace the contents of that line with whatever the admin enters now.
else
{
//convert the users specified line number from string to integer.
line = Convert.ToInt32(editLine);
//Make sure the line number specified isn't greater than the number
//of lines we currently have.
if (realm.DetailedDescription.Count >= line)
{
//Get the new description for this line...
player.Send("New Description: ", false);
input = player.ReadInput();
//-1 correction due to collection index starting at 0 and not 1.
//replace the existing description with the new one.
realm.DetailedDescription[line - 1] = input;
}
//Let the admin know that the line number specified does not exist.
else
{
player.Send("Line Editing canceled. The Realm does not contain that many lines.");
}
}
}
//If he/she typed 'clear' then we need to check if the admin wants to clear
//every single line of description or just a single line.
else if (input.ToLower().StartsWith("clear"))
{
//Find out what additional content is included with the clear input.
String clear = input.ToLower().Substring("clear".Length).Trim();
//if no additional values supplied with the clear command, we cancel.
if (String.IsNullOrEmpty(clear))
{
player.Send("Line Clearing canceled. You need to supply a line number or specify 'All' to clear all lines.");
}
//Admin specified to clear 'all' lines of the descrpition.
else if (clear == "all")
{
//Wipe out every line of description.
realm.DetailedDescription.Clear();
}
//Admin specified a single line. Find the admins specified line number for clearing.
else
{
//Convert the specified line number to a integer
Int32 i = Convert.ToInt32(clear);
//make sure the line number provided does in-fact exist.
if (realm.DetailedDescription.Count >= i)
//Remove the specified line number for the descriptions collection.
realm.DetailedDescription.Remove(realm.DetailedDescription[i - 1]);
//Line provided is larger than the number of lines available to check. Cancel.
else
player.Send("Line Clearing canceled. The Realm does not contain that many description lines.");
}
}
//No special tokens provided, so we assume this line is a description.
//Add the contents to the realm's detailed description collection.
else
{
realm.DetailedDescription.Add(input);
}
}
//Loop is finished, so lets save the game world.
player.ActiveGame.Save();
player.Send("Detailed Description saved.");
break;
}
}
/// <summary>
/// This method parses the values supplied by the admin from the Name editing menu.
/// Allows for editing visible names and filenames
/// </summary>
/// <param name="value"></param>
private void ParseNameSelection(Int32 value)
{
switch (value)
{
//Admin wants to edit the visible name
case 1:
player.FlushConsole();
player.Send("Enter a new Visible name for this Realm.");
player.Send("Enter Value: ", false);
//Get the new name for this Realm
String newName = player.ReadInput();
//Ensure the new name is not blank.
if (String.IsNullOrEmpty(newName))
{
player.Send("Realm Name Editing canceled. No name supplied.");
}
//We have a valid name, so lets try assigning it to the Realm
else
{
//Check to see if the supplied name already exists by checking for a existing Realm
//that has a matching filename.
if (player.ActiveGame.World.GetRealm(newName + ".Realm") == null)
{
//No matching Realm was found, so we can go ahead and make the change.
//Store the new name within this Realm
String oldName = realm.Filename;
realm.Name = newName;
//Update all of the objects that are within the Realm to have the
//new name assigned to them.
UpdateRealmObjects(oldName);
//TODO: Any Items/NPC's etc within this Realm will need to be updated as well.
}
else
{
player.Send("Realm Name Editing canceled. " + newName + " already exists within the World.");
player.Send("If you want to keep the provided Visible Name for this Realm, you must create a unique Filename.");
player.Send("Would you like to assign a unique Filename now?");
player.Send("1: Yes");
player.Send("2: No");
try
{
Int32 i = Convert.ToInt32(player.ReadInput());
switch (i)
{
case 1:
player.FlushConsole();
player.Send("The default filename for this Realm (using the new Visible Name) is currently:");
player.Send(newName + ".Realm");
player.Send("Please supply a different filename:");
player.Send("Enter Value: ", false);
String filename = player.ReadInput();
if (String.IsNullOrEmpty(filename))
{
player.Send("Realm Name Editing Canceled. No filename supplied.");
}
else
{
if (player.ActiveGame.World.GetRealm(filename) != null)
{
player.Send("Realm Name Editing Canceled. A Realm with this filename already exists!");
}
else
{
String oldName = realm.Filename;
realm.Name = newName;
realm.Filename = filename;
UpdateRealmObjects(oldName);
}
}
break;
case 2:
player.Send("Realm Name Editing Canceled. A Realm with this filename already exists!");
break;
}
}
catch
{
player.Send("Realm Name Editing canceled. The supplied value was not numeric.");
}
}
}
player.ActiveGame.Save();
break;
//Filename
case 2:
player.FlushConsole();
player.Send("Enter a new Filenamename for this Realm.");
player.Send("Enter Value: ", false);
String fname = player.ReadInput();
if (String.IsNullOrEmpty(fname))
{
player.Send("Realm Name Editing canceled. No name supplied.");
}
else if (player.ActiveGame.World.GetRealm(fname) != null)
{
player.Send("Realm Name Editing canceled. A Realm with this filename already exists!");
}
else
{
String oldName = realm.Filename;
realm.Filename = fname;
UpdateRealmObjects(oldName);
player.ActiveGame.Save();
}
break;
}
}
private void UpdateRealmObjects(String oldName)
{
//Check if this Realm is the initial Realm. If so then we need to update the
//current Game so that when it launches, it checks the new Realm and not the old
//one.
if (realm.IsInitialRealm)
{
player.ActiveGame.InitialRealm = realm;
}
//Loop through each player currently logged in and see if they are currently
//within the selected Realm. If they are then we need to change their current
//Realm to the new one.
foreach (BaseCharacter p in player.ActiveGame.GetPlayerCollection())
{
if (p.Name.StartsWith("Base"))
continue;
if (p.CurrentRoom.Realm == oldName)
{
p.CurrentRoom.Realm = realm.Filename;
p.Save(player.ActiveGame.DataPaths.Players);
}
}
//Loop through every player on file to see if any are currently within
//the selected Realm. If they are, we need to edit their files so that they
//log-in next time into the correct Realm. Otherwise Admins will need to manually
//edit the files and move the players.
//This is done after scanning logged in players, as logged in players will of had
//their location already updated and files saved, preventing any need for us to
//modify their files again.
foreach (String file in Directory.GetFiles(player.ActiveGame.DataPaths.Players))
{
BaseCharacter ch = new BaseCharacter(player.ActiveGame);
ch.Load(file);
if (ch.CurrentRoom.Realm == oldName)
{
ch.CurrentRoom.Realm = realm.Filename;
ch.Save(player.ActiveGame.DataPaths.Players);
}
}
//Next, we need to loop through every Zone and Door within this Realm
//and update their Realm names to match the new one
foreach (Zone z in realm.ZoneCollection)
{
if (z.Realm == oldName)
z.Realm = realm.Filename;
foreach (Room r in z.RoomCollection)
{
if (r.Realm == oldName)
r.Realm = realm.Filename;
}
}
}
}
}

View file

@ -6,6 +6,8 @@ using System.Text;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using MudEngine.GameManagement;
namespace MudEngine.FileSystem namespace MudEngine.FileSystem
{ {
/// <summary> /// <summary>
@ -54,6 +56,11 @@ namespace MudEngine.FileSystem
public static String GetData(String filename, String name) public static String GetData(String filename, String name)
{ {
if (!File.Exists(filename))
{
Log.Write("Error: Failed attempting to load " + filename + ". File does not exist.");
return "No data Found." ;
}
foreach (String line in File.ReadAllLines(filename)) foreach (String line in File.ReadAllLines(filename))
{ {
//Ignore comments //Ignore comments

View file

@ -219,7 +219,7 @@ namespace MudEngine.GameManagement
PlayerCollection[i] = new BaseCharacter(this); PlayerCollection[i] = new BaseCharacter(this);
AutoSave = true; AutoSave = true;
AutoSaveInterval = 1; AutoSaveInterval = 60;
MinimumPasswordSize = 6; MinimumPasswordSize = 6;
} }
@ -356,7 +356,7 @@ namespace MudEngine.GameManagement
FileManager.WriteLine(filename, this.GameTitle, "GameTitle"); FileManager.WriteLine(filename, this.GameTitle, "GameTitle");
FileManager.WriteLine(filename, this.HideRoomNames.ToString(), "HideRoomNames"); FileManager.WriteLine(filename, this.HideRoomNames.ToString(), "HideRoomNames");
if (this.InitialRealm.Name != "New Realm") if ((this.InitialRealm != null) && (this.InitialRealm.Name != "New Realm"))
FileManager.WriteLine(filename, this.InitialRealm.Filename, "InitialRealm"); FileManager.WriteLine(filename, this.InitialRealm.Filename, "InitialRealm");
FileManager.WriteLine(filename, this.IsMultiplayer.ToString(), "IsMultiplayer"); FileManager.WriteLine(filename, this.IsMultiplayer.ToString(), "IsMultiplayer");
@ -387,6 +387,8 @@ namespace MudEngine.GameManagement
return; return;
Log.Write("Restoring Game Settings..."); Log.Write("Restoring Game Settings...");
try
{
this.AutoSave = Convert.ToBoolean(FileManager.GetData(filename, "AutoSave")); this.AutoSave = Convert.ToBoolean(FileManager.GetData(filename, "AutoSave"));
this.AutoSaveInterval = Convert.ToInt32(FileManager.GetData(filename, "AutoSaveInterval")); this.AutoSaveInterval = Convert.ToInt32(FileManager.GetData(filename, "AutoSaveInterval"));
this.BaseCurrencyAmount = Convert.ToInt32(FileManager.GetData(filename, "BaseCurrencyAmount")); this.BaseCurrencyAmount = Convert.ToInt32(FileManager.GetData(filename, "BaseCurrencyAmount"));
@ -417,6 +419,12 @@ namespace MudEngine.GameManagement
break; break;
} }
} }
}
catch
{
Log.Write("Critical Error: Unable to complete Game.Load() due to error during loading of file data.");
return;
}
//Restore the world. //Restore the world.
World.Load(); World.Load();

View file

@ -129,6 +129,10 @@ namespace MudEngine.GameManagement
isFound = true; isFound = true;
break; break;
} }
else
{
RealmCollection.Add(r);
}
} }
if (!isFound) if (!isFound)

View file

@ -53,9 +53,33 @@ namespace MudEngine.GameObjects
{ {
//Returns the name of the object + the objects Type as it's extension. //Returns the name of the object + the objects Type as it's extension.
//Filenames are generated by the class itself, users can not assign it. //Filenames are generated by the class itself, users can not assign it.
get; get
set; {
return _Filename;
} }
set
{
if (this.GetType().Name.StartsWith("Base"))
{
if (value.EndsWith("." + this.GetType().Name.Substring("Base".Length)))
{
_Filename = value;
}
else
_Filename = value + "." + this.GetType().Name.Substring("Base".Length);
}
else
{
if (value.EndsWith("." + this.GetType().Name))
{
_Filename = value;
}
else
_Filename =value + "." + this.GetType().Name;
}
}
}
String _Filename;
[Category("Environment Information")] [Category("Environment Information")]
[Description("If a user asks to use his/her senses to investigate an area, this is one of the results that will be displayed. Senses can be used to assist blind characters.")] [Description("If a user asks to use his/her senses to investigate an area, this is one of the results that will be displayed. Senses can be used to assist blind characters.")]

View file

@ -316,9 +316,12 @@
else else
CurrentRoom = ActiveGame.InitialRealm.InitialZone.InitialRoom; CurrentRoom = ActiveGame.InitialRealm.InitialZone.InitialRoom;
ExecuteCommand("Login"); IGameCommand gc = CommandEngine.GetCommand("CommandLogin");
gc.Execute("Login", this);
Log.Write(Name + " has logged in."); Log.Write(Name + " has logged in.");
ExecuteCommand("Look"); //MUST happen after Room setup is completed, otherwise the player default Abyss Room is printed. gc = CommandEngine.GetCommand("CommandLook");
gc.Execute("Look", this); //MUST happen after Room setup is completed, otherwise the player default Abyss Room is printed.
this.Send("Command: ", false);
} }
internal void Receive(String data) internal void Receive(String data)
{ {

View file

@ -53,6 +53,7 @@
<Compile Include="Commands\CommandRestart.cs" /> <Compile Include="Commands\CommandRestart.cs" />
<Compile Include="Commands\CommandLogin.cs" /> <Compile Include="Commands\CommandLogin.cs" />
<Compile Include="Commands\CommandSaveWorld.cs" /> <Compile Include="Commands\CommandSaveWorld.cs" />
<Compile Include="Commands\CommandEditRealm.cs" />
<Compile Include="FileSystem\SaveDataPaths.cs" /> <Compile Include="FileSystem\SaveDataPaths.cs" />
<Compile Include="GameManagement\CommandEngine.cs" /> <Compile Include="GameManagement\CommandEngine.cs" />
<Compile Include="GameManagement\GameTime.cs" /> <Compile Include="GameManagement\GameTime.cs" />

View file

@ -42,6 +42,6 @@ public class EarthGame : Game
Cali = new WorldCalifornia(this); Cali = new WorldCalifornia(this);
//Calling the create method within the california script. //Calling the create method within the california script.
Cali.Create(); //Cali.Create();
} }
} }