Added and implemented a basic command-line parser.

This replaces the switch-case in HandleCommandLine and HandleCommandLinePostConfigLoad
This commit is contained in:
White 2017-03-13 11:40:44 +10:30
parent 047f9e7475
commit fc7460c7d5
5 changed files with 600 additions and 202 deletions

View file

@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace TShockAPI.CLI
{
/// <summary>
/// A simple command-line parser for retrieving basic information from a command-line. Array types are not supported
/// </summary>
public class CommandLineParser
{
private List<FlagSet> _flags = new List<FlagSet>();
private Dictionary<FlagSet, object> _results = new Dictionary<FlagSet, object>();
private string[] _source;
/// <summary>
/// Resets the CommandLineParser, removing any results and flags, and clearing the source
/// </summary>
/// <returns></returns>
public CommandLineParser Reset()
{
_flags.Clear();
_results.Clear();
_source = null;
return this;
}
/// <summary>
/// Adds a flag to be parsed
/// </summary>
/// <param name="flag">The flag to be added</param>
/// <param name="noArgs">Whether or not the flag is followed by an argument</param>
public CommandLineParser AddFlag(string flag, bool noArgs = false)
{
FlagSet flags = new FlagSet(flag) { NoArgs = noArgs };
return AddFlags(flags);
}
/// <summary>
/// Adds a flag to be parsed, with the given callback being invoked with the flag's argument when it is found.
/// The callback's parameter is the argument passed to the flag
/// </summary>
/// <param name="flag"></param>
/// <param name="callback"></param>
/// <returns></returns>
public CommandLineParser AddFlag(string flag, Action<string> callback)
{
FlagSet flags = new FlagSet(flag) { callback = callback };
return AddFlags(flags);
}
/// <summary>
/// Adds a flag to be parsed, with the given callback being invoked when the flag is found.
/// This method assumes the flag has no arguments
/// </summary>
/// <param name="flag"></param>
/// <param name="callback"></param>
/// <returns></returns>
public CommandLineParser AddFlag(string flag, Action callback)
{
FlagSet flags = new FlagSet(flag) { NoArgs = true, callback = callback };
return AddFlags(flags);
}
/// <summary>
/// Adds a range of flags to be parsed
/// </summary>
/// <param name="flags">The FlagSet to be added</param>
/// <returns></returns>
public CommandLineParser AddFlags(FlagSet flags)
{
if (_flags.Contains(flags))
{
return this;
}
_flags.Add(flags);
return this;
}
/// <summary>
/// Adds a range of flags to be parsed, with the given callback being invoked with the flag's argument when it is found.
/// The callback's parameter is the argument passed to the flag
/// </summary>
/// <param name="flags">The FlagSet to be added</param>
/// <param name="callback">An Action with a single string parameter. This parameter is the value passed to the flag</param>
/// <returns></returns>
public CommandLineParser AddFlags(FlagSet flags, Action<string> callback)
{
flags.callback = callback;
return AddFlags(flags);
}
/// <summary>
/// Adds a range of flags to be parsed, with the given callback being invoked when the flag's argument is found.
/// This method assumes the flag has no arguments
/// </summary>
/// <param name="flags">The FlagSet to be added</param>
/// <param name="callback">An Action with no parameters.</param>
/// <returns></returns>
public CommandLineParser AddFlags(FlagSet flags, Action callback)
{
flags.callback = callback;
flags.NoArgs = true;
return AddFlags(flags);
}
/// <summary>
/// Adds a callback after a flag's parsing has been completed.
/// This method automatically attaches the callback to the last added flag
/// </summary>
/// <param name="callback">An Action with no parameters.</param>
/// <returns></returns>
public CommandLineParser After(Action callback)
{
FlagSet flags = _flags.Last();
flags.continuation = callback;
return this;
}
/// <summary>
/// Gets the result of a FlagSet, cast to the given type parameter. Array types are not supported
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="flags"></param>
/// <returns></returns>
public T Get<T>(FlagSet flags)
{
if (!_results.ContainsKey(flags))
{
return default(T);
}
object result = _results[flags];
Type t = typeof(T);
if (t == typeof(string))
{
if (result == null)
{
return (T)(object)string.Empty;
}
return (T)result;
}
if (t.IsValueType)
{
TypeConverter tc = TypeDescriptor.GetConverter(t);
return (T)tc.ConvertFromString(result.ToString());
}
return (T)Activator.CreateInstance(t, result);
}
/// <summary>
/// Parses the given source for flags registered with the parser
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public CommandLineParser ParseFromSource(string[] source)
{
_source = source;
for (int i = 0; i < source.Length - 1; i++)
{
string flag = source[i].ToLowerInvariant();
string argument = source[i + 1];
FlagSet flags = _flags.FirstOrDefault(f => f.Contains(flag));
if (flags == null)
{
continue;
}
if (flags.NoArgs)
{
if (flags.callback != null)
{
((Action)flags.callback).Invoke();
}
else
{
_results.Add(flags, true);
}
}
else
{
if (flags.callback != null)
{
((Action<string>)flags.callback).Invoke(argument);
}
else
{
_results.Add(flags, argument);
}
}
flags.continuation?.Invoke();
}
return this;
}
/// <summary>
/// Gets the result of a flag, cast to the given type parameter. Array types are not supported
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="flag"></param>
/// <returns></returns>
public T Get<T>(string flag)
{
FlagSet flags = _flags.FirstOrDefault(f => f.Contains(flag));
if (flags == null)
{
return default(T);
}
return Get<T>(flags);
}
/// <summary>
/// Attempts to get the result of a flag, cast to the given type parameter. Array types are not supported
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="flag"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool TryGet<T>(string flag, out T value)
{
FlagSet flags = _flags.FirstOrDefault(f => f.Contains(flag));
if (flags == null)
{
value = default(T);
return false;
}
return TryGet(flags, out value);
}
/// <summary>
/// Attempts to get the result of a FlagSet, cast to the given type parameter. Array types are not supported
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="flags"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool TryGet<T>(FlagSet flags, out T value)
{
object result = _results[flags];
if (result == null)
{
//Null result shouldn't happen, but return false if it does
value = default(T);
return false;
}
Type t = typeof(T);
//Strings get special handling because the result object is a string
if (t == typeof(string))
{
if (result == null)
{
//Null strings shouldn't happen, but return false if it does
value = default(T);
return false;
}
value = (T)result;
return true;
}
//Value types get converted with a TypeConverter
if (t.IsValueType)
{
try
{
TypeConverter tc = TypeDescriptor.GetConverter(t);
value = (T)tc.ConvertFrom(result);
return true;
}
catch
{
value = default(T);
return false;
}
}
try
{
//Reference types get created with an Activator
value = (T)Activator.CreateInstance(t, result);
return true;
}
catch
{
value = default(T);
return false;
}
}
}
}

71
TShockAPI/CLI/FlagSet.cs Normal file
View file

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace TShockAPI.CLI
{
/// <summary>
/// Describes a set of flags that are responsible for one CL argument
/// </summary>
public class FlagSet : IEquatable<FlagSet>
{
private IEnumerable<string> _flags;
internal object callback;
internal Action continuation;
/// <summary>
/// Whether or not the set of flags represented by this FlagSet is followed by an argument
/// </summary>
public bool NoArgs { get; set; }
/// <summary>
/// Creates a new <see cref="FlagSet"/> with the given flags
/// </summary>
/// <param name="flags">Flags represented by this FlagSet</param>
public FlagSet(params string[] flags)
{
if (flags == null)
{
throw new ArgumentNullException(nameof(flags));
}
_flags = flags.Select(f => f.ToLowerInvariant());
}
/// <summary>
/// Creates a new <see cref="FlagSet"/> with the given flags and arguments option
/// </summary>
/// <param name="flags">Flags represented by this FlagSet</param>
/// <param name="noArgs">Whether or not the flags specified will be followed by an argument</param>
public FlagSet(string[] flags, bool noArgs) : this(flags)
{
NoArgs = noArgs;
}
/// <summary>
/// Determines whether or not this flag set contains the given flag
/// </summary>
/// <param name="flag"></param>
/// <returns></returns>
public bool Contains(string flag)
{
return _flags.Contains(flag);
}
/// <summary>
/// Determines whether or not this flag set is equatable to another
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(FlagSet other)
{
if (other == null)
{
return false;
}
return other._flags == _flags;
}
}
}

View file

@ -41,6 +41,7 @@ using TShockAPI.ServerSideCharacters;
using Terraria.Utilities;
using Microsoft.Xna.Framework;
using TShockAPI.Sockets;
using TShockAPI.CLI;
namespace TShockAPI
{
@ -127,6 +128,10 @@ namespace TShockAPI
/// <summary>instance - Static reference to the TerrariaPlugin instance.</summary>
public static TerrariaPlugin instance;
/// <summary>
/// Static reference to a <see cref="CommandLineParser"/> used for simple command-line parsing
/// </summary>
public static CommandLineParser CliParser { get; } = new CommandLineParser();
/// <summary>
/// Used for implementing REST Tokens prior to the REST system starting up.
/// </summary>
public static Dictionary<string, SecureRest.TokenData> RESTStartupTokens = new Dictionary<string, SecureRest.TokenData>();
@ -204,6 +209,7 @@ namespace TShockAPI
try
{
CliParser.Reset();
HandleCommandLine(Environment.GetCommandLineArgs());
if (!Directory.Exists(SavePath))
@ -295,6 +301,7 @@ namespace TShockAPI
File.WriteAllText(Path.Combine(SavePath, "tshock.pid"),
Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));
CliParser.Reset();
HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
Backups = new BackupManager(Path.Combine(SavePath, "backups"));
@ -609,217 +616,202 @@ namespace TShockAPI
/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
private void HandleCommandLine(string[] parms)
{
string path;
for (int i = 0; i < parms.Length; i++)
string path = null;
//Generic method for doing a path sanity check
Action<string> pathChecker = (p) =>
{
switch (parms[i].ToLower())
if (!string.IsNullOrWhiteSpace(p) && p.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
case "-configpath":
{
path = parms[++i];
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
SavePath = path;
ServerApi.LogWriter.PluginWriteLine(this, "Config path has been set to " + path, TraceLevel.Info);
}
break;
}
case "-worldpath":
{
path = parms[++i];
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
Main.WorldPath = path;
ServerApi.LogWriter.PluginWriteLine(this, "World path has been set to " + path, TraceLevel.Info);
}
break;
}
case "-logpath":
{
path = parms[++i];
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
LogPath = path;
ServerApi.LogWriter.PluginWriteLine(this, "Log path has been set to " + path, TraceLevel.Info);
}
break;
}
case "-logformat":
{
LogFormat = parms[++i];
break;
}
case "-logclear":
{
bool.TryParse(parms[++i], out LogClear);
break;
}
case "-dump":
{
Utils.PrepareLangForDump();
Lang.setLang(true);
ConfigFile.DumpDescriptions();
Permissions.DumpDescriptions();
ServerSideConfig.DumpDescriptions();
RestManager.DumpDescriptions();
Utils.DumpBuffs("BuffList.txt");
Utils.DumpItems("Items-1_0.txt", -48, 235);
Utils.DumpItems("Items-1_1.txt", 235, 604);
Utils.DumpItems("Items-1_2.txt", 604, 2749);
Utils.DumpItems("Items-1_3.txt", 2749, Main.maxItemTypes);
Utils.DumpNPCs("NPCs.txt");
Utils.DumpProjectiles("Projectiles.txt");
Utils.DumpPrefixes("Prefixes.txt");
Environment.Exit(1);
break;
}
case "-config":
{
string filePath = parms[++i];
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Loading dedicated config file: {0}", filePath), TraceLevel.Verbose);
Main.instance.LoadDedConfig(filePath);
break;
}
case "-port":
{
int serverPort;
if (int.TryParse(parms[++i], out serverPort))
{
Netplay.ListenPort = serverPort;
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Listening on port {0}.", serverPort), TraceLevel.Verbose);
}
else
{
// The server should not start up if this argument is invalid.
throw new InvalidOperationException("Invalid value given for command line argument \"-ip\".");
}
break;
}
case "-worldname":
{
string worldName = parms[++i];
Main.instance.SetWorldName(worldName);
ServerApi.LogWriter.PluginWriteLine(this, string.Format("World name will be overridden by: {0}", worldName), TraceLevel.Verbose);
break;
}
case "-autoshutdown":
{
Main.instance.EnableAutoShutdown();
break;
}
case "-autocreate":
{
string newOpt = parms[++i];
Main.instance.autoCreate(newOpt);
break;
}
case "-ip":
{
IPAddress ip;
if (IPAddress.TryParse(parms[++i], out ip))
{
Netplay.ServerIP = ip;
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Listening on IP {0}.", ip), TraceLevel.Verbose);
}
else
{
// The server should not start up if this argument is invalid.
throw new InvalidOperationException("Invalid value given for command line argument \"-ip\".");
}
break;
}
case "-connperip":
{
int limit;
if (int.TryParse(parms[++i], out limit))
{
/* Todo - Requires an OTAPI modification
Netplay.MaxConnections = limit;
ServerApi.LogWriter.PluginWriteLine(this, string.Format(
"Connections per IP have been limited to {0} connections.", limit), TraceLevel.Verbose);*/
ServerApi.LogWriter.PluginWriteLine(this, "\"-connperip\" is not supported in this version of TShock.", TraceLevel.Verbose);
}
else
ServerApi.LogWriter.PluginWriteLine(this, "Invalid value given for command line argument \"-connperip\".", TraceLevel.Warning);
break;
}
case "-killinactivesocket":
{
// Netplay.killInactive = true;
ServerApi.LogWriter.PluginWriteLine(this, "The argument -killinactivesocket is no longer present in Terraria.", TraceLevel.Warning);
break;
}
case "-lang":
{
int langIndex;
if (int.TryParse(parms[++i], out langIndex))
{
Lang.lang = langIndex;
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Language index set to {0}.", langIndex), TraceLevel.Verbose);
}
else
ServerApi.LogWriter.PluginWriteLine(this, "Invalid value given for command line argument \"-lang\".", TraceLevel.Warning);
break;
}
case "--provider-token":
{
StatTracker.ProviderToken = parms[++i];
break;
}
case "--stats-optout":
{
StatTracker.OptOut = true;
break;
}
case "--no-restart":
{
TShock.NoRestart = true;
break;
}
path = p;
}
}
};
//Prepare the parser with all the flags available
CliParser
.AddFlag("-configpath", pathChecker)
//The .After Action is run after the pathChecker Action
.After(() =>
{
SavePath = path ?? "tshock";
if (path != null)
{
ServerApi.LogWriter.PluginWriteLine(this, "Config path has been set to " + path, TraceLevel.Info);
}
})
.AddFlag("-worldpath", pathChecker)
.After(() =>
{
if (path != null)
{
Main.WorldPath = path;
ServerApi.LogWriter.PluginWriteLine(this, "World path has been set to " + path, TraceLevel.Info);
}
})
.AddFlag("-logpath", pathChecker)
.After(() =>
{
if (path != null)
{
LogPath = path;
ServerApi.LogWriter.PluginWriteLine(this, "Log path has been set to " + path, TraceLevel.Info);
}
})
.AddFlag("-logformat", (format) =>
{
if (!string.IsNullOrWhiteSpace(format)) { LogFormat = format; }
})
.AddFlag("-config", (cfg) =>
{
if (!string.IsNullOrWhiteSpace(cfg))
{
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Loading dedicated config file: {0}", cfg), TraceLevel.Verbose);
Main.instance.LoadDedConfig(cfg);
}
})
.AddFlag("-port", (p) =>
{
int port;
if (int.TryParse(p, out port))
{
Netplay.ListenPort = port;
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Listening on port {0}.", port), TraceLevel.Verbose);
}
})
.AddFlag("-worldname", (world) =>
{
if (!string.IsNullOrWhiteSpace(world))
{
Main.instance.SetWorldName(world);
ServerApi.LogWriter.PluginWriteLine(this, string.Format("World name will be overridden by: {0}", world), TraceLevel.Verbose);
}
})
.AddFlag("-ip", (ip) =>
{
IPAddress addr;
if (IPAddress.TryParse(ip, out addr))
{
Netplay.ServerIP = addr;
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Listening on IP {0}.", addr), TraceLevel.Verbose);
}
else
{
// The server should not start up if this argument is invalid.
throw new InvalidOperationException("Invalid value given for command line argument \"-ip\".");
}
})
.AddFlag("-lang", (l) =>
{
int lang;
if (int.TryParse(l, out lang))
{
Lang.lang = lang;
ServerApi.LogWriter.PluginWriteLine(this, string.Format("Language index set to {0}.", lang), TraceLevel.Verbose);
}
else
{
ServerApi.LogWriter.PluginWriteLine(this, "Invalid value given for command line argument \"-lang\".", TraceLevel.Warning);
}
})
.AddFlag("-autocreate", (size) =>
{
if (!string.IsNullOrWhiteSpace(size))
{
Main.instance.autoCreate(size);
}
})
.AddFlag("--provider-token", (token) => StatTracker.ProviderToken = token)
//Flags without arguments
.AddFlag("-logclear", () => LogClear = true)
.AddFlag("-autoshutdown", () => Main.instance.EnableAutoShutdown())
.AddFlag("-dump", () => Utils.Dump())
.AddFlag("--stats-optout", () => StatTracker.OptOut = true)
.AddFlag("--no-restart", () => NoRestart = true);
CliParser.ParseFromSource(parms);
/*"-connperip": Todo - Requires an OTAPI modification
{
int limit;
if (int.TryParse(parms[++i], out limit))
{
//Netplay.MaxConnections = limit;
//ServerApi.LogWriter.PluginWriteLine(this, string.Format(
// "Connections per IP have been limited to {0} connections.", limit), TraceLevel.Verbose);
ServerApi.LogWriter.PluginWriteLine(this, "\"-connperip\" is not supported in this version of TShock.", TraceLevel.Verbose);
}
else
ServerApi.LogWriter.PluginWriteLine(this, "Invalid value given for command line argument \"-connperip\".", TraceLevel.Warning);
}*/
}
/// <summary>HandleCommandLinePostConfigLoad - Handles additional command line options after the config file is read.</summary>
/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
public static void HandleCommandLinePostConfigLoad(string[] parms)
{
for (int i = 0; i < parms.Length; i++)
{
switch (parms[i].ToLower())
{
case "-port":
int port = Convert.ToInt32(parms[++i]);
Netplay.ListenPort = port;
Config.ServerPort = port;
OverridePort = true;
Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
break;
case "-rest-token":
string token = Convert.ToString(parms[++i]);
FlagSet portSet = new FlagSet("-port");
FlagSet playerSet = new FlagSet("-maxplayers", "-players");
FlagSet restTokenSet = new FlagSet("--rest-token", "-rest-token");
FlagSet restEnableSet = new FlagSet("--rest-enabled", "-rest-enabled");
FlagSet restPortSet = new FlagSet("--rest-port", "-rest-port");
CliParser
.AddFlags(portSet, (p) =>
{
int port;
if (int.TryParse(p, out port))
{
Netplay.ListenPort = port;
Config.ServerPort = port;
OverridePort = true;
Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
}
})
.AddFlags(restTokenSet, (token) =>
{
RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" });
Console.WriteLine("Startup parameter overrode REST token.");
break;
case "-rest-enabled":
Config.RestApiEnabled = Convert.ToBoolean(parms[++i]);
Console.WriteLine("Startup parameter overrode REST enable.");
break;
case "-rest-port":
Config.RestApiPort = Convert.ToInt32(parms[++i]);
})
.AddFlags(restEnableSet, (e) =>
{
bool enabled;
if (bool.TryParse(e, out enabled))
{
Config.RestApiEnabled = enabled;
Console.WriteLine("Startup parameter overrode REST enable.");
}
})
.AddFlags(restPortSet, (p) =>
{
int restPort;
if (int.TryParse(p, out restPort))
{
Config.RestApiPort = restPort;
Console.WriteLine("Startup parameter overrode REST port.");
break;
case "-maxplayers":
case "-players":
Config.MaxSlots = Convert.ToInt32(parms[++i]);
Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
break;
}
}
}
})
.AddFlags(playerSet, (p)=>
{
int slots;
if (int.TryParse(p, out slots))
{
Config.MaxSlots = slots;
Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
}
});
CliParser.ParseFromSource(parms);
}
/// <summary>AuthToken - The auth token used by the /auth system to grant temporary superadmin access to new admins.</summary>
@ -1014,7 +1006,8 @@ namespace TShockAPI
if (player.TilePlaceThreshold >= Config.TilePlaceThreshold)
{
player.Disable("Reached TilePlace threshold", flags);
lock (player.TilesCreated) {
lock (player.TilesCreated)
{
TSPlayer.Server.RevertTiles(player.TilesCreated);
player.TilesCreated.Clear();
}

View file

@ -83,6 +83,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BackupManager.cs" />
<Compile Include="CLI\CommandLineParser.cs" />
<Compile Include="CLI\FlagSet.cs" />
<Compile Include="DB\ProjectileManager.cs" />
<Compile Include="DB\RegionManager.cs" />
<Compile Include="DB\TileManager.cs" />
@ -201,7 +203,7 @@
</PropertyGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_IncrementBeforeBuild="False" BuildVersion_StartDate="2011/6/17" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_BuildAction="Both" BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" />
<UserProperties BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" BuildVersion_BuildAction="Both" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_StartDate="2011/6/17" BuildVersion_IncrementBeforeBuild="False" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View file

@ -1166,6 +1166,32 @@ namespace TShockAPI
return points;
}
/// <summary>
/// Dumps information and optionally exits afterwards
/// </summary>
/// <param name="exit"></param>
public void Dump(bool exit = true)
{
PrepareLangForDump();
Lang.setLang(true);
ConfigFile.DumpDescriptions();
Permissions.DumpDescriptions();
ServerSideCharacters.ServerSideConfig.DumpDescriptions();
RestManager.DumpDescriptions();
DumpBuffs("BuffList.txt");
DumpItems("Items-1_0.txt", -48, 235);
DumpItems("Items-1_1.txt", 235, 604);
DumpItems("Items-1_2.txt", 604, 2749);
DumpItems("Items-1_3.txt", 2749, Main.maxItemTypes);
DumpNPCs("NPCs.txt");
DumpProjectiles("Projectiles.txt");
DumpPrefixes("Prefixes.txt");
if (exit)
{
Environment.Exit(1);
}
}
internal void PrepareLangForDump()
{
for(int i = 0; i < Main.recipe.Length; i++)