MineCraft 服务器管理






4.70/5 (11投票s)
一个包装应用程序,使管理、修改和运行多个 MineCraft 服务器更快、更容易。
这个项目发展迅速,现已转移到 CodePlex!请前往该网站获取最新的发布和代码!
此项目现在有自己的官方网站 ManageMinecraft.com!
- 下载应用程序 .Net 4.0 限制版本 - 26.7 KB
- 下载源代码 .Net 4.0 限制版本 - 390.3 KB
以上需要 .Net 4.0,但不包含 Mod 安装程序窗体 - 下载应用程序 V2 - 29.5 KB
- 下载源代码 V2 - 1 MB
以上需要 .Net 4.5
注意:程序必须在 64 位配置下编译 - 在 VS2010 中,转到“生成” - “配置管理器...” - 然后在 64 位下设置。原因请参阅底部的“兴趣点”。
引言
我开发的应用程序通过将标准输入/输出与用户友好的命令按钮包装起来,使多个 MineCraft 服务器的管理变得更加容易和快捷,同时仍然允许扩展和 MineCraft 的全部功能。该应用程序解决了学习 MineCraft 服务器最常用命令的问题,并使编辑设置更加可靠。
背景
我最近才开始玩 MineCraft,很快就发现最好的形式是和朋友一起在线玩。这意味着需要一个服务器,由于我有一个良好的互联网连接,并且对电脑有一些了解,我决定搭建一个服务器。然而,我很快就厌倦了尝试记住命令和管理诸如白名单之类的设置。因此,我决定一个易于使用的管理程序将是最好的解决方案,于是就有了这个程序。
使用代码
使用代码的简单方法当然是下载应用程序并启动它。用户界面应该非常直观且易于使用,控件在可用/不可用时会启用/禁用。我还包含了一个基本的调度系统,允许您选择服务器开始/停止的时间。该应用程序允许您选择包含所有服务器文件夹的文件夹,然后(或跳过此步骤)选择包含您希望运行的服务器的文件夹。然后它会允许您选择要启动的可用世界以及服务器运行的端口。端口号从服务器的配置中加载,因此如果您想保持不变,则无需更改。最后需要注意的是带有选项卡的命令按钮,可以添加或删除,以及底部的命令行输入,允许输入任何命令和输入要从命令按钮添加/删除的命令。
代码结构
代码有一个基本但希望有用的结构,分为两个部分
后端代码
- MineCraft 服务器类
- 服务器设置类
前端用户界面
- 主窗体
- 命令参数窗体
- 设置窗体
类
MineCraftServer 类: 这封装了与 MineCraft 服务器本身(即 Java 应用程序实例)的所有最低级别交互。
ServerSettings 类: 允许轻松编辑/访问主要的服务器设置文件。目前包括 server.properties、ops.txt、white-list.txt 和 banned-players.txt。可以轻松添加对其他文件(如 banned-ips.txt)的支持,但我认为总体上用处较小,因此尚未包含。未来的版本可能会。
OutputEvent.cs: 为与 MineCraft 服务器输出的更友好交互提供(非常)基本的事件功能。
窗体
主窗体: 程序启动时加载的主窗口。它执行所有从注册表加载先前设置的初始操作,允许选择该实例的服务器,并提供对程序所有其余部分的访问(直接通过按钮 - 没有讨厌的菜单、子菜单或窗口层层打开才能找到所需内容)。
命令参数窗体: 构造函数接受一个参数,该参数允许指定用户必须输入的参数标签文本。文本框和标签根据这些值动态添加。确定和取消按钮已添加到窗体中(未动态添加)——此处动态指通过我自己的代码添加,而不是通过 IDE 生成的代码。
设置窗体: 用于不同文件的选项卡控件,以及一个“通用”选项卡,提供用户友好的控件,用于主要(几乎所有)server.properties 设置(例如,用于启用 PvP、怪物等的复选框)。可扩展,并提供一个“所有”选项卡,允许将任何键/值对(包括值为空的注释)添加到 server.properties 文件中 - 使用此选项卡上的列表框显示可选键。
它们的工作原理
MineCraftServer 类
基本原理是以与 launch.bat 或 MineCraft.exe 程序非常相似的方式启动 MineCraft 服务器,但随后获取输出以在我的程序中显示它,并重定向输入以便我的程序可以控制它。长期以来(据我所知,至少在 Windows 上),这已内置到大多数控制台应用程序中,以 StandardInput、StandardOutput 和 StandardError 流的形式,前者可写入作为输入,后两者可读取作为输出。
所以,首先,如何从 C# 启动另一个应用程序?答案是使用 System.Diagnostics.Process
,然后使用 Start
方法。(我仍在寻找一个令人满意的理由,说明为什么它被放在 Diagnostics 下,因为它看起来有点奇怪——有人知道吗?)为此,您需要(或者更确切地说更喜欢)创建 System.Diagnostics.ProcessStartInfo
。以下代码可以在 Launch
方法中找到。
string JarFileName = GetJarFileName(ServerFolderAddress);
if (string.IsNullOrEmpty(JarFileName))
{
throw new FileNotFoundException(/*Content removed for article.*/);
}
ProcessStartInfo ServerStartInfo = new ProcessStartInfo(@"java",
"-Xmx3G -Xms2G -jar " + JarFileName + " nogui -nojline")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = ServerFolderAddress,
};
if ((ServerProcess = Process.Start(ServerStartInfo)) != null)
您看到的第一行是 GetJarFileName
。它的作用是在指定目录中搜索 MineCraft 服务器 jar 文件的存在。然而,由于实际识别 jar 文件内容相当困难,我通过基于具有以下名称之一的 jar 文件(单词首字母大写和缺少下划线已考虑在内)的存在来完成此操作。
- Tekkit.jar(将找到 tekkit.jar)
- CraftBukkit.jar(将找到 craftBukkit.jar、Craftbukkit.jar 和 craftbukkit.jar)
- Minecraft_server.jar(将找到上述相关排列,同样没有下划线)。
注意:程序不允许您使用 MineCraft_server.exe 文件或任何 launch.bat 文件。
为什么它不这样做?嗯,.exe 和 .bat 程序只是启动 jar 文件,但带有正确的命令行参数。我已经在我的程序中完成了这一点(基本上做了 Minecraft.exe 所做的事情)。然而,由于它们启动了一个新应用程序,如果我们尝试获取 .exe 或批处理程序的输入/输出,它们将无济于事,我们需要实际 Java 程序的 I/O。此外,为此,我们需要传递给 Java 一个额外的参数才能使其工作,这是我们无法通过 exe 或 bat 程序完成的。
因此,代码会查找 MineCraft 服务器 jar 文件,如果存在,则返回其文件名(不是完整的地址名称),如果不存在,则返回 null。然后进行测试以查看是否找到 jar 文件,如果未找到,则抛出异常以通知调用代码(从而通知用户)。
接下来,代码创建 ProcessStartInfo
。它的作用是设置所有要传递给我们要启动的进程文件(由文件名/地址指定)的参数。它还执行诸如重定向标准 I/O 流之类的操作,这是我们在服务器启动后控制它所需的。
ProcessStartInfo
接受两个参数(在默认构造函数中)。它们是:filename 和 arguments。
文件名只是“Java”,因为我们只想运行机器上安装的最新版 Java。
参数部分变得有趣。第一个参数(“-Xmx3G”)指定 Java 应尝试从 RAM 分配给程序的内存最大量。3G 表示 3 千兆字节,低于此值服务器似乎拒绝运行。下一个参数相同(“-Xms2G”),但指定要分配的最小量——在本例中为 2 千兆字节。您可以尝试修改并将其更改为 1024M(M 代表兆字节)之类的东西——其表示法非常简单,可以轻松查找。
下一个参数(“-jar”)告诉 Java 我们要打开一个 jar 文件,然后我们指定要运行的应用程序的文件名——特别是文件名而不是文件地址,因为我们稍后会指定要启动的工作目录。
我们几乎告诉它没有图形用户界面(“nogui”),尽管这不是必不可少的,因为我们稍后会指定不为进程创建窗口。
最后是最重要的论点。由于未知原因,MineCraft(或者也许是 Java 本身)默认不使用 StandardOutput、StandardError 或 StandardInput 流,或者至少它不将它们链接起来。此参数对于使 MineCraft(或 Java)使用它们以使程序能够获取服务器的输入/输出绝对至关重要。
关于 exited 事件的最后一点说明。虽然并非绝对必要,但它是一种清理进程和清除事件的好方法。我还用它将 ServerRunning
设置为 false。我建议在启动您控制的进程时使用此事件,就像这里所做的那样。
服务器设置类
请注意,该类要求您在执行任何其他操作之前加载设置。您无法使用它从头开始创建设置文件。这是一个可能的改进,已在最后列出。
ServerSettings
类提供了对以下服务器文件的快速便捷访问
- server.properties:主要的服务器属性文件。严格来说,这是一个 YAML 文件(我想),但我将其视为每行一个键/值对的文件,因为目前没有任何属性使用其他 YAML 结构。
- banned-players.txt:被封禁的用户列表。可以为 banned-ips.txt 添加类似的支持,但我认为 banned-ips 的用处较小。
- white-list.txt
- ops.txt
ServerSettings
类包含一个名为 SettingsFile
的可枚举类型 - 它用于指定您希望访问哪个设置文件(不包括 server.properties 文件)。稍后会更清楚,但我想在这里说,如果您希望扩展该类以包含 banned-ips.txt 或类似文件,那么添加一个名为 IPBlackList
的选项将是明智的。
您可以对设置文件执行四种基本操作。
- 加载它 - 涉及将文件读入内存/更可用的列表或字典形式
- 添加/编辑它 - 涉及将键/值对或仅值添加到字典/列表或替换现有值 - 这些是非常相似的操作,可以使用相同的语法完成,因此我将它们归为一类。
- 从中删除 - 涉及删除键/值对或值。虽然键/值对可以用相同的方法完成(将键设置为
null
),但列表不行,所以这是一个单独的操作。 - 保存它 - 涉及将新的(内存中)版本以正确的格式保存到文件中。
1. 加载设置文件
代码目前处理上述四个文件,但代码可以很容易地用于加载其他文件(如前所述)。代码如下所示,为便于解释,我在代码中添加了注释,否则(我担心)可能会让人困惑
//The file path to the server.properties file
string SettingsFilePath = Path.Combine(ServerFolderAddress, "server.properties");
//Check the file exists
if (File.Exists(SettingsFilePath))
{
//If it does, create FileInfo for it to give us easy access to useful file information
SettingsFileInfo = new FileInfo(SettingsFilePath);
//Read all the text from the file
SettingsDataString = File.ReadAllText(SettingsFileInfo.FullName);
//Converts the text into a Dictionary<string, string> where comments
//(denoted by # at start) have no value, only key.
//Invalid lines that are not comments will (or at least should) be ignored.
//1. Split the data into separate lines (without empty lines)
//2. Convert each line into a key/value pair using '=' as the separator
SettingsData = SettingsDataString.Split("\r\n".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries).ToDictionary(
//Get the key from the data
delegate(string x)
{
//If the line is a comment, return the whole line
if (x.Trim()[0] == '#')
{
return x;
}
//Otherwise, return just the bit before the equals sign - the key
else
{
//Split by equals, return the first bit,
//remove white space from the beginning and end of it
return x.Split('=').First().Trim();
}
},
//Get the value from the data
delegate(string x)
{
//Split the data using equals sign
string[] SplitStrings = x.Split('=');
//Check if the key is actually a comment and that there is a value for this key.
//Empty values are accepted as the StringSplitOptions.RemoveEmptyEntries is not set
//If this were an invalid entry (i.e. without 'key=value' but just 'key',
//it would be treated as an empty entry
if (SplitStrings[0].Trim()[0] != '#' && SplitStrings.Length >= 2)
{
//Return just the value,
//removing any possible remnants of its file origin - i.e. new lines
return SplitStrings[1].Trim().Replace("\r", "").Replace("\n", "");
}
else
{
//Return nothing since the whole comment is in the key
return "";
}
});
}
//If we couldn't find the main settings file
//something has gone very wrong since it is required by MineCraft
else
{
//Hence throw an exception
throw new FileLoadException("Could not find main, required settings file!");
}
//Get the path of the white list
string WhiteListFilePath = Path.Combine(ServerFolderAddress, "white-list.txt");
//Check the white list exists
if (File.Exists(WhiteListFilePath))
{
//Create white list FileInfo
WhiteListFileInfo = new FileInfo(WhiteListFilePath);
//Load white list data into a string
WhiteListDataString = File.ReadAllText(WhiteListFileInfo.FullName);
//Load white list data into a list, split by new line, ignore empty lines.
WhiteListData = WhiteListDataString.Split("\r\n".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries).ToList();
}
由于黑名单(被禁玩家列表)和管理员(操作员)列表的结构与白名单(允许玩家列表)相同,因此我没有将它们包含在上述代码示例中。
2. 添加/编辑内存中的设置
该代码提供了两种将键/值对添加到 server.properties 设置的方法,以及一种添加到其他设置列表的方法。
添加键/值对的方法
- 将键设置为一个值。代码重载了方括号运算符,允许按键索引 server.properties 设置。您可以将不存在的键设置为一个值,或将现有键设置为一个新值。此方法在设置以前未设置的键时使用下一个方法。
Add(string key, string value)
方法。仅通过查看它就相当不言自明。它只是调用底层Dictionary
对象的Add
方法。
添加值的方法
由于该类允许添加到三个不同的列表(黑名单、白名单和操作员列表),所有这些列表都具有相同的格式,因此使用相同的方法,您只需指定哪个列表。该方法本身调用相关的底层 List
对象的 Add
方法。它还会检查您是否尝试添加重复项,因此会阻止这种情况发生。该方法接受两个参数,第二个是值,第一个是前面提到的 SettingsFile
可枚举类型中的一个选项 - 注意:此参数不应作为标志使用 - 只会处理一个。
还可以使用方括号运算符对不同的设置文件进行索引。它接受两个参数,第一个是(仅限其中一个)SettingsFile
可枚举选项,第二个是您想要的列表中值的整数索引。如果索引超出范围,将抛出错误。
此时值得指出的是,方括号运算符的重载方式与其他运算符(如 +、-、/、* 等)不同。要重载它,您只需以下一行,然后是正常的 get/set(或仅 get)代码
public Type this[Type key]
//or even (and as I have used):
public Type this[Type Option, Type key] // which has multiple arguments
注意:类型可以是您喜欢的任何类型,而不仅仅是泛型类型,例如它可以是 string
。此外,每种类型都可以替换为不同的类型 - 它们不必都相同。
以下是使用
运算符完成的方法:(显然 MSDN 使用了一个 Complex 数字类,但它可以替换为您喜欢的任何类。)+
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}
//Adapted from: http://msdn.microsoft.com/en-us/library/aa288467(v=vs.71).aspx
差异(据我所知)是由于方括号被称为下标运算符,而 +、-、/、* 等是单目运算符(等也包括二目运算符)。进一步有用的阅读是以下两个 MSDN 页面
- http://msdn.microsoft.com/en-us/library/aa288467(v=vs.71).aspx - 运算符重载教程
- http://msdn.microsoft.com/en-us/library/5tk49fh2.aspx - 运算符重载 - 运算符完整列表
3. 从内存中删除设置
我在这些章节的标题中写“内存中”,因为这就是它的本质——写“从设置文件中删除”会产生误导,因为它不涉及文件操作。如果在调用这些方法后查看文件,您会发现没有变化——您必须调用 Save
才能在文件中看到效果。
要从 server.properties 列表中删除键/值对,再次有两种选择
- 将键的值设置为 null - 这将使用下面的方法。注意:将其设置为空或空白字符将保留键,但将值设置为空或空白字符,只有 null 会将其从列表中完全删除。代码示例:
MySettings["HelloWorld"] = null;
- 调用
Remove(string key)
方法。这将调用底层Dictionary
对象的Remove
方法。
要从其他列表之一中删除项目,请使用 Remove(SettingsFile file, string value)
方法 - 其工作方式与 Add
方法等效。
4. 将内存中的设置保存到设置文件
此过程分为两个阶段。第一阶段是将列表(和字典)数据转换为正确的字符串表示形式。第二阶段是将该文本保存到相关文件。
生成字符串表示
首先,简单的事情,创建白名单、黑名单和操作员列表的字符串形式非常容易。只需遍历所有值,并在每个值之后添加一个新行。具体如下
//Clear the data string
BlackListDataString = "";
//Use LINQ to write a compact for loop method for the list.
BlackListData.ForEach(delegate(string AName)
{
//For the sake of it use Environment.NewLine - equiv. of \n
BlackListDataString += AName + Environment.NewLine;
});
我选择使用 LINQ 进行 for each 方法,仅仅因为它快速、简单、紧凑。对于我们关心的任何事情,它都没有计算上的增益或损失(您可能会争辩说速度可能会受到影响或提高,但在本应用程序中确实不会引人注目)。我想借此机会简要谈谈 delegate
函数。这是一种在内联代码中编写短函数而无需创建全新方法的功能强大方式,特别是当代码只使用一次时。然而,最近我注意到一些代码使用了相同的委托函数,并且在各处复制。我强烈不鼓励这样做,因为它会导致难以更新/维护的代码,因为您有许多重复的行。我建议使用委托,但要小心 - 不要到处复制它们!
接下来是稍微复杂一点的 server.properties 字典信息的字符串表示的创建。我不能说带有 Dictionary
对象的委托函数与 Lists
一样友好。代码很快就会变得难以阅读,所以我选择在这里使用一个合适的 For-Each 循环。代码如下
//Clear the data string
SettingsDataString = "";
//Loop through all the key value pairs
foreach (KeyValuePair<string, string> ASetting in SettingsData)
{
//Is the pair a comment?
if (ASetting.Key.Trim()[0] == '#')
{
//If so just add the key - value should be blank but if it isn't,
//ignore it as it's not wanted.
SettingsDataString += ASetting.Key + Environment.NewLine;
}
else
{
//Else if it's not a comment, add the key, the equals separator
//then the value and finally a new line.
SettingsDataString += ASetting.Key + "=" + ASetting.Value + Environment.NewLine;
}
}
你可能想知道为什么我在测试注释时选择调用 Trim
,但没有添加键?嗯,考虑到文件应该是 YAML 格式,用户可能希望输入非常特定的值(带有空格)以产生所需的结果。因此,测试注释时使用 Trim
,就像 MineCraft 会做的那样,但添加键/值时我不这样做,因为任何额外的空格很可能是故意的。该代码旨在为未来的 MineCraft 版本提供相当长的使用寿命。
OutputEvent.cs 文件
此文件封装了在服务器收到输出时用于我自己的自定义事件的必要类和委托。可以在 MSDN 上找到有关如何创建自己的事件的良好教程,网址为
http://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx
否则,只需说此文件包含一个 OutputEventArgs
类,其中包含从服务器接收到的文本。
设置窗体
在选择了服务器文件夹后,可以从主窗体访问设置窗体。它在其构造函数中接受一个参数,即所选服务器文件夹的地址。设置窗体在您关闭它时才会保存对设置文件的任何更改,届时您可以选择保存或不保存。
它包含什么?一个带有以下选项卡的选项卡控件
-
通用 - 拥有用于主要 server.properties 设置的用户友好控件。这包括用于服务器端口的数字上下调节器;用于布尔选项(例如启用 PvP、启用怪物)的复选框;用于世界名称的文本框(设置为 level-name);用于级别类型的下拉框;用于每日消息的文本框。表单底部有一个绿色的“设置值”按钮 - 必须点击此按钮才能设置您更改的值。在切换到其他选项卡之前,请确保已点击“设置值”按钮。
-
所有 - 具有 server.properties 文件中找到的设置的列表框和键/值输入。有设置和删除按钮。
-
黑名单 - 提供列表框、值输入框和添加/删除按钮。
-
白名单 - 与黑名单相同。
-
管理员 - 与黑名单相同。
表单代码使用已描述的 ServerSettings 类。它在创建表单实例时加载所有设置,并在关闭时向用户提供是或否的选择,以决定是否保存任何更改。
主窗体
现在我们来到程序的主窗体,它将所有功能连接在一起。
(过时图片 - 这是程序的第一版)
表单控件按使用顺序线性排列。它们在可用/不可用时也会启用/禁用。首先,您可以选择一个包含所有 MineCraft 服务器子文件夹的文件夹,这些子文件夹将显示在下面的下拉框中。或者您可以直接选择要使用的 MineCraft 服务器文件夹,跳过上一步。这就是为什么有两个浏览按钮。该程序旨在使服务器切换非常容易和快速。因此,当您选择要使用的服务器时,基本设置将加载到“世界”和“端口”框中。这允许您快速选择要启动服务器运行的可用世界(该框是一个下拉列表,您也可以在其中键入以创建新世界)。“端口号”框是一个数字上下调节器,允许快速选择要运行服务器的端口。当您启动服务器时,这些设置首先会自动保存到 server.properties 文件中,因此服务器会使用这些设置启动。该表单显然还有“服务器设置”按钮,可打开“设置”表单。选定的服务器文件夹和服务器文件夹地址保存在注册表中,因此当您下次启动程序时它们仍然存在,从而大大加快了速度。
还有有用的“开始时间”、“结束时间”、“开始”和“停止”按钮。这些允许您设置服务器应该运行的时间和应该停止的时间。屏幕截图显示这些时间设置为早上 8 点和午夜。检查这些值的计时器(计时器在您单击“开始”按钮时启动)每 15 分钟跳动一次,因此时间精确到 15 分钟。这意味着服务器实际上可能在 8:15 而不是 8:00 开始。此外,如果您在您所说的希望服务器运行的时间间隔内启动计时器,则服务器将立即启动。即,使用上述设置,如果我在下午 1 点启动计时器,则服务器将立即启动,在午夜停止,然后在第二天早上 8 点再次重新启动。最后,您设置的时间也保存在注册表中,因此当您下次启动时它们仍然存在(但程序不会自动启动计时器!)。
然后您会看到“启动”按钮、“停止”按钮和旁边的“输出”框。“启动”按钮允许您启动服务器运行,“停止”按钮允许您停止服务器运行。“停止”按钮也作为覆盖停止按钮,将停止服务器和计时器(如果计时器正在运行)以防止其重新启动。输出框显示通常在命令提示符输出窗口中显示的所有文本。但是,在向服务器发送命令后,通常会发送一个额外的空白命令,因此预计会看到服务器输出(命令之后的某个位置)“未找到命令。使用 /help 获取命令列表”——或类似的话。我不知道这是为什么,如果有人知道和/或可以修复它,我将不胜感激。
下方是命令按钮选项卡控件和命令输入框。选项卡控件允许您添加无限数量的选项卡,每个选项卡有 12 个大小相等、自定义的命令按钮。默认选项卡包含所有 MineCraft 服务器都拥有(需要)的标准命令按钮,您还可以在屏幕截图中看到我为 MobArena 插件命令创建的自定义选项卡。要添加命令按钮,请选择要添加到的选项卡(但不是默认选项卡),然后在命令框中键入您希望该按钮关联的命令。最后,单击添加命令按钮。通过在命令框中键入完全相同的文本,您可以从自定义按钮中删除该命令按钮。对于具有额外参数的命令,使用的语法与 MineCraft wiki 文档中使用的伪语法相同,即
“命令 [命令参数 - 允许空格] [命令参数] 其他命令文本”
例如 "msg [玩家姓名] [消息]"
参数名称中允许有空格。当您单击按钮时,系统会要求您输入参数名称。请参阅稍后对“命令参数窗体”的解释。
选项卡和按钮在程序关闭时保存在注册表中,所以不用担心每次启动程序时都要重新设置它们。另外,按钮不依赖于您使用的服务器。
最后,要向服务器发送您喜欢的任何命令,只需在命令框中输入它并按回车键。这包括停止命令,但我强烈建议您不要通过命令框发出停止命令,因为它不会被管理程序的其余部分识别。
所以,这就是如何使用程序,现在来看看代码是如何工作的。
选择服务器文件夹并在其中查找服务器文件夹
您做的第一件事是输入包含您服务器文件夹(或单个文件夹)的文件夹地址。您还可以使用浏览按钮,它只是打开一个文件夹浏览器对话框,非常基础。但一旦您选择它,代码就会将所有包含可运行服务器的子文件夹添加到“服务器文件夹”组合框中。您当然可以使用文件夹浏览器对话框再次选择确切的文件夹。要讨论的代码是如何检测文件夹是否是服务器文件夹?(核心,精简)代码如下所示
//Get a list of top-level only directories that could be server folders - top-level only
//prevents needlessly searching through the masses of sub-folders that servers have.
string[] SubDirectories = Directory.GetDirectories(ServersFolderAddressBox.Text, "*",
SearchOption.TopDirectoryOnly);
//Loop through all of them
for (int i = 0; i < SubDirectories.Length; i++)
{
//Check to see if they contain a valid server Jar file - uses previously discussed
//method for getting Jar file name but wrapped up in the ContainsServerJarFile method.
if (MineCraftServer.ContainsServerJarFile(SubDirectories[i]))
{
//If it does, add it to the drop down list of possible server folders.
ServerFolderAddressBox.Items.Add(SubDirectories[i]);
}
}
这使用了我在 MineCraftServer
类中描述的方法来检测文件夹中是否存在默认的 MineCraft 服务器 jar 文件。如果存在,则可以使用该服务器,并将其添加到列表中。使用的类位于 System.IO
中,是 Directory
和 File
类,它们提供对文件/目录处理标准方法的快速简便访问。
加载服务器基本配置:世界和端口
为此,代码使用已描述的 ServerSettings
类,然后将 level-name 和 server-port 设置加载到相关框中。代码还会搜索以 _nether 结尾的文件夹。为什么?因为在 Bukkit MineCraft 服务器(包括基于 Bukkit 的 Tekkit)下,它会将您的世界分成三个独立的文件夹 - 其中一个以 _nether 结尾。因此,这是一种快速简便的方法来识别服务器文件夹中的世界。它不适用于标准的 MineCraft 服务器,因此对于这些服务器,列表将为空,但是,即使找不到世界文件夹,世界名称也会自动从配置中加载。
除此之外,除了最后几项,主窗体的大部分代码都相当无聊
- 命令参数窗体
- 使用注册表加载和保存应用程序设置
对于“命令参数”窗体,我决定创建一个窗体,它会根据传递给它的所需参数名称列表,动态地向其界面添加文本框和标签。这提供了最大的灵活性,但代码仍然轻量级。我将相同的窗体用于添加选项卡页面和命令按钮,因为它提供了一种简单的方式来获取用户关于新选项卡页面名称和新命令按钮文本等内容的输入。
最后,从/向注册表加载和保存应用程序设置。我使用注册表键/子键结构以以下格式保存选项卡及其命令按钮
App 注册表键(文件夹)
- Tab 页子键(文件夹,名称 = "CommandPage_" + Tab 页名称)
- 命令按钮键
键 = 命令 + 按钮编号
值 = 命令 + "¬" + 按钮文本
使用“CommandPage_”可以让我识别作为选项卡页面的子键,而不是假设所有子键都是选项卡页面——这允许在未来版本中实现更大的可扩展性。
加载然后保存的代码如下
//Get all the sub keys then filter by whether they start with "CommandPage_"
List<string> CommandPagesSubKeyNames = Application.UserAppDataRegistry.GetSubKeyNames()
.Where(x => x.Split('_')[0] == "CommandPage").ToList();
//For each of the sub keys identified as being a tab page
foreach (string APageSubKeyName in CommandPagesSubKeyNames)
{
//Get the page name
string PageName = "";
//Split the name by underscore
string[] KeyParts = APageSubKeyName.Split('_');
//Get all the parts apart from the fist one (replacing underscores) - the name may have
//originally contained underscores
for(int i = 1; i < KeyParts.Length; i++)
{
PageName += KeyParts[i] + "_";
}
//Remove the final underscore
PageName = PageName.Remove(PageName.Length - 1);
//Create the new tab page
TabPage NewPage = new TabPage(PageName);
//Add it to the tab control
CommandsTabControl.TabPages.Add(NewPage);
//Get the pages registry sub key
Microsoft.Win32.RegistryKey CommandPageSubKey = Application.UserAppDataRegistry.OpenSubKey(APageSubKeyName);
//Get all the sub keys - which will be command buttons.
string[] KeyNames = CommandPageSubKey.GetValueNames();
//Loop through all of them - this could do with a check for max buttons allowed.
for (int i = 0; i < KeyNames.Length; i++)
{
Button NewCommandButton = new Button();
//Boring code for setting up button position goes here.
//Get the value of the sub key
string Val = (string)CommandPageSubKey.GetValue(KeyNames[i]);
//Command button text is the second bit
NewCommandButton.Text = Val.Split('¬')[1];
//The command itself is the second bit
Commands.Add(Val.Split('¬')[0], NewCommandButton);
//Add the command button to the tab page
NewPage.Controls.Add(NewCommandButton);
}
}
//Get all the existing tab page sub keys
List<string> CommandPagesSubKeyNames = Application.UserAppDataRegistry.GetSubKeyNames().Where(
x => x.Split('_')[0] == "CommandPage").ToList();
//And loop through to delete them - this is easiest way of clearing out for a new save.
foreach (string APageSubKeyName in CommandPagesSubKeyNames)
{
Application.UserAppDataRegistry.DeleteSubKeyTree(APageSubKeyName, false);
}
//Now loop through all the command button tab pages adding in the relevant keys.
foreach(TabPage ACommandPage in CommandsTabControl.TabPages)
{
//If this isn't the default tab page
if (ACommandPage.Text != "Default")
{
//Create a new key for this page
Microsoft.Win32.RegistryKey ThisPageKey = Application.UserAppDataRegistry.CreateSubKey(
"CommandPage_" + ACommandPage.Text);
//Store the button number
int i = 0;
//Loop through all the buttons
foreach (Button ACommandButton in ACommandPage.Controls)
{
//Get the command for this button
string ACommand = (from Comds in Commands
where (Comds.Value == ACommandButton)
select Comds.Key).First();
//Add a new key for this button is above described format
ThisPageKey.SetValue("CommandButton" + (i++).ToString(),
ACommand + "¬" + ACommandButton.Text);
}
}
}
与 MineCraft、Bukkit 和 Tekkit 的兼容性
新代码(我希望)提供了与原版 Minecraft 服务器、Bukkit 和 Tekkit 服务器的完全兼容性。如果您遇到任何问题或发现任何功能未能正常工作,请告诉我什么不起作用以及您正在使用哪个版本的 Minecraft、Bukkit 或 Tekkit。
Mod 安装程序
Mod 安装程序窗体利用 Jar 文件的 zip 基础,将它们转换,安装 Mod,然后再转换回 Jar 文件。Mod 安装程序使用最新版本的 .Net 框架 (4.5),该版本仍处于 Beta 阶段,因为它包含新的 System.IO.Compression,专门提供处理 zip 存档的类。代码获取 Jar 文件,将其扩展名更改为 .zip,打开它,将选定文件夹中的文件复制到 zip 中所需位置,覆盖任何现有文件,最后将扩展名更改回 .jar。此方法适用于大多数 Mod,但是,它存在一些问题。Jar 和 Zip 确实有一些细微的差异,但我目前无法分辨它们是什么。这意味着一些流行的 Mod(特别是 ModLoader 或 ModLoaderMP)无法正确安装。它们会导致一个大的致命错误,实质上是说 ModLoader 无法正确读取 jar 文件。此错误发生在许多 zip 处理软件中,例如 7Zip 和 ZipArchiver(尽管它并非始终发生),但 WinRAR 似乎不会导致此问题。因此,我建议,在我解决此问题之前,请保留您的 WinRAR 副本。
客户端 Mod
Mod 安装程序窗体还能够修改客户端 jar 文件。为此,只需选择“%appdata%\.minecraft”文件夹作为服务器文件夹地址值,然后选择“bin”作为服务器文件夹。像往常一样使用 Mod 安装程序,它将在 minecraft.jar 文件中安装 Mod。
private void InstallButton_Click(object sender, EventArgs e)
{
//Set total progress to zero
TotalProgressBar.Value = 0;
//Disable the form so that the user can't change stuff while we are installing mods
this.Enabled = false;
//Create FileInfo for the server's JAR file
FileInfo FileInf = new FileInfo(ServerPath);
//Create a string for the new destination of the file - the only change here is changing the file to a .zip extension.
//I have not tried it without this change, it might be worth trying at some point.
string DestZipFileName = FileInf.FullName.Replace(FileInf.Extension, ".zip");
//Miove the file to the new file name, basically just changes the extension of the file.
File.Move(ServerPath, DestZipFileName);
//Open the ZipArchive (class is from System.IO.Compression namepsace.)
using (ZipArchive ServerArchive = ZipFile.Open(DestZipFileName, ZipArchiveMode.Update))
{
//Set the maximum value of the TotalProgressBar to the number of mods we are installing
TotalProgressBar.Maximum = ModsListBox.Items.Count;
//For every mod listed, work through them in order.
for(int i = 0; i < ModsListBox.Items.Count; i++)
{
//Get the next mod
ListViewItem Item = ModsListBox.Items[i];
//Update UI to show we are installing this mod
TotalProgressBar.Value++;
//Set the current progress through this mod to zero.
CurrentProgressBar.Value = 0;
//get all the files in the mod's directory and sub directories
FileInfo[] Files = new DirectoryInfo(Item.Text).GetFiles("*.*", SearchOption.AllDirectories);
//Set the maximum progress to how many files there are in this mod.
CurrentProgressBar.Maximum = Files.Length;
//Loop through all the mod's files
foreach (FileInfo file in Files)
{
//Update UI to show progress
CurrentProgressBar.Value++;
//Get the name of the entry that this file will be in the Zip file.
//If the destination sub folder is blank, then don't add a leading backslash
//Use the files full path so as to keep the direcotry structure in the zip file
//but remove the spurious back slahes and the leading (root) folder Uri.
string NewEntryName = (!string.IsNullOrWhiteSpace(Item.SubItems[1].Text) ?
Item.SubItems[1].Text + "/" : "") +
file.FullName.Replace(Item.Text + "\\ " , "");
//Replace back slahes with forward slahes - format of entry names
NewEntryName = NewEntryName.Replace("\\ ", "/");
//Get any existing file in the zip
ZipArchiveEntry fileInZip = (from f in ServerArchive.Entries
where f.FullName == NewEntryName
select f).FirstOrDefault();
//If there is an existing file (entry), delete it
if (fileInZip != null)
{
fileInZip.Delete();
}
//Add our new entry using fast compression.
ServerArchive.CreateEntryFromFile(file.FullName, NewEntryName, CompressionLevel.Fastest);
}
}
//If user wishes to delete the Meta-Inf folder, do so as below.
if (DeleteMatInfCheckBox.Checked)
{
//Get all entries in the Meta-Inf folder
List<ZipArchiveEntry> MetaInfFiles = (from f in ServerArchive.Entries
where f.FullName.Contains("META-INF")
select f).ToList();
//And delete them
MetaInfFiles.ForEach(x => x.Delete());
//Deleting all entries remove the Meta-Inf "folder"
}
}
//Change the extension back to .jar
File.Move(DestZipFileName, ServerPath);
}
编辑器
编辑器窗体目前只允许您查看玩家背包中的物品及其 x、y、z 坐标。它实际上不允许编辑。我将其放入以预览未来窗体的样子。它使用 Substrate C# 库(我推荐)来读取(和稍后写入)玩家数据 (.dat) 文件。默认情况下,它会列出至少 36 个物品,但如果例如您正在使用 Hack/Mine mod,它会添加额外的背包空间,则会列出更多物品。
新 UI
最新版本引入了一些新的 UI 功能,包括
-
消息日志框
-
已连接玩家框
-
所有玩家框
-
相关按钮
消息日志框主要输出玩家在游戏中或从控制台发送的消息。但是,它还会列出“给予”和“发出命令”消息——当用户试图给自己物品或发出服务器命令时。这些类型的消息还会导致任务栏托盘图标闪烁,直到您查看程序——它会提醒您潜在的作弊行为。我很快将发布一个新版本,允许您关闭此功能。最后列出的几条消息是用户加入或离开时。所有消息都只格式化为时间、用户名、消息/信息。
当检测到加入或离开消息时,已连接玩家框会更新。它列出所有当前连接的玩家,旁边有各种有用的命令按钮,包括踢出、封禁和消息。
所有玩家框列出所有玩家,并在服务器启动时从“players”数据文件夹加载。当有新玩家在服务器运行时加入时,它也会更新。它带有两个命令按钮:赦免和编辑。编辑按钮打开编辑窗体,尽管目前无法编辑,只能查看物品/位置。
加载世界
此版本的新功能是根据文件夹内容而非文件夹名称加载世界。代码查找“level.dat”文件,然后将其目录名称用作世界名称。为避免与 Bukkit(或 Tekkit)出现问题,代码还会检查目录名称是否包含“_nether”或“_the_end”,以防止它列出世界的各个部分。
停止按钮改进——也停止计时器
停止按钮代码的改进意味着它现在除了停止服务器外还会停止计时器,以前它只停止服务器,如果计时器仍在运行,服务器将在 15 分钟内重新启动。
更新
- 应用程序现在使用 .Net Framework 4.5 - 您需要安装此框架才能使其完整版本正常工作。可能只附带 Visual Studio 11 Beta - 我不确定...
- 根据文件夹内容而非名称搜索世界
- Mod 安装程序窗体 - 允许从文件夹安装 Mod(即解压您的 Mod 压缩文件),然后选择目标文件夹并开始!如果您愿意,它甚至会删除 Meta-Inf 文件夹。Modding 也适用于客户端。只需选择客户端的 bin 文件夹作为服务器文件夹。此功能利用了最新的 .Net 框架版本 (4.5)。它使用改进的
System.IO.Compression
命名空间进行 zip 处理,即 jar 文件处理。 - 用户界面改进,例如
: 添加了消息日志框,以友好的格式列出消息,包括
- 玩家和控制台的消息
- “给予”消息 - 用于检测玩家通过给自己物品作弊:任务栏图标闪烁,直到主窗口被查看。
- 连接/断开连接消息
- “命令”消息 - 用于检测发布服务器命令的作弊玩家:任务栏图标闪烁,直到主窗口被查看。
: 添加了已连接玩家列表框和相关的命令按钮
: 添加了所有玩家列表框(从数据文件加载)和一些有用的命令按钮 - 编辑玩家窗体 - 使用 Substrate MineCraft C# 库 - 可在 Google Code 此处找到。目前不允许编辑用户物品,但如果可用,将列出物品 ID 和其他信息。还将告诉您他们的 x、y、z 坐标。
- 用户界面和功能中的小错误修复
关注点
学到的最奇怪、最令人沮丧的事情是 MineCraft(或 Java)需要“-nojline”参数才能修复的怪癖。
同样有趣的是,32 位机器在任何给定时间只能分配最多 4G 的物理内存,因此程序需要编译为 64 位。要使 32 位版本工作,我需要实验 MineCraft 服务器实际需要/将接受多少内存。
有用链接
- MineCraft 命令列表:http://www.minecraftwiki.net/wiki/SMP_Server_commands
- MineCraft 属性文档:http://www.minecraftwiki.net/wiki/Server.properties
- MineCraft 服务器设置教程:http://www.minecraftwiki.net/wiki/Setting_up_a_server
历史
12年4月7日 - 首次发布
12年5月13日 - 首次更新。主要新功能:Mod 安装程序、消息日志框、编辑玩家窗体