UltimateZoneBuilder/Source/Core/General/General.cs

2271 lines
70 KiB
C#
Executable file

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Plugins;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Types;
using CodeImp.DoomBuilder.Windows;
#endregion
namespace CodeImp.DoomBuilder
{
public static class General
{
#region ================== API Declarations and Mono compatibility
#if MONO_WINFORMS
public static void ApplyMonoListViewFix(System.Windows.Forms.ListView listview)
{
if (listview.View == System.Windows.Forms.View.List)
{
listview.View = System.Windows.Forms.View.SmallIcon;
}
}
public static void ApplyDataGridViewFix(System.Windows.Forms.DataGridView gridview)
{
if (gridview.RowsDefaultCellStyle != null && gridview.RowsDefaultCellStyle.Padding != new System.Windows.Forms.Padding(0,0,0,0))
{
gridview.RowsDefaultCellStyle.Padding = new System.Windows.Forms.Padding(0,0,0,0);
}
}
#else
public static void ApplyMonoListViewFix(System.Windows.Forms.ListView listview) {}
public static void ApplyDataGridViewFix(System.Windows.Forms.DataGridView gridview) {}
#endif
#if NO_WIN32
internal static void InvokeUIActions(MainForm mainform)
{
// This implementation really should work universally, but it seemed to hang sometimes on Windows.
// Let's hope the mono implementation of Winforms works better.
mainform.Invoke(new System.Action(() => { mainform.ProcessQueuedUIActions(); }));
}
internal static bool MessageBeep(MessageBeepType type)
{
System.Media.SystemSounds.Beep.Play();
return true;
}
internal static bool LockWindowUpdate(IntPtr hwnd)
{
// This can be safely ignored. It is a performance/flicker optimization. It might not even be needed on Windows anymore.
return true;
}
internal unsafe static void ZeroPixels(PixelColor* pixels, int size)
{
var transparent = new PixelColor(0,0,0,0);
for (int i = 0; i < size; i++)
pixels[i] = transparent;
}
internal static void SetComboBoxItemHeight(ComboBox combobox, int height)
{
// Only used by FieldsEditorControl. Not sure what its purpose is, might only be visual adjustment that isn't strictly needed?
}
#else
[DllImport("user32.dll")]
internal static extern bool LockWindowUpdate(IntPtr hwnd);
[DllImport("kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)]
static extern void ZeroMemory(IntPtr dest, int size);
internal unsafe static void ZeroPixels(PixelColor* pixels, int size) { ZeroMemory(new IntPtr(pixels), size * sizeof(PixelColor)); }
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern int SendMessage(IntPtr hwnd, uint Msg, IntPtr wParam, IntPtr lParam);
internal static void SetComboBoxItemHeight(ComboBox combobox, int height)
{
SendMessage(combobox.Handle, General.CB_SETITEMHEIGHT, new IntPtr(-1), new IntPtr(height));
}
[DllImport("user32.dll", EntryPoint = "PostMessage", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern int PostMessage(IntPtr hwnd, uint Msg, IntPtr wParam, IntPtr lParam);
internal static void InvokeUIActions(MainForm mainform)
{
PostMessage(mainform.Handle, General.WM_UIACTION, IntPtr.Zero, IntPtr.Zero);
}
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MessageBeep(MessageBeepType type);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint GetShortPathName([MarshalAs(UnmanagedType.LPTStr)] string longpath, [MarshalAs(UnmanagedType.LPTStr)]StringBuilder shortpath, uint buffersize);
#endif
#endregion
#region ================== Constants
// SendMessage API
internal const int WM_USER = 0x400;
internal const int WM_UIACTION = WM_USER + 1;
internal const int WM_SYSCOMMAND = 0x112;
internal const int WM_MOUSEHWHEEL = 0x020E; // [ZZ]
internal const int SC_KEYMENU = 0xF100;
internal const int CB_SETITEMHEIGHT = 0x153;
//internal const int CB_SHOWDROPDOWN = 0x14F;
//internal const int EM_GETSCROLLPOS = WM_USER + 221;
//internal const int EM_SETSCROLLPOS = WM_USER + 222;
//internal const int SB_HORZ = 0;
//internal const int SB_VERT = 1;
//internal const int SB_CTL = 2;
//internal const int SIF_RANGE = 0x1;
//internal const int SIF_PAGE = 0x2;
//internal const int SIF_POS = 0x4;
//internal const int SIF_DISABLENOSCROLL = 0x8;
//internal const int SIF_TRACKPOS = 0x16;
//internal const int SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS;
// Files and Folders
private const string LEGACY_SETTINGS_FILE = "GZBuilder.cfg"; // To make transision from GZDB* easier
private const string SETTINGS_FILE = "UDBuilder.cfg";
private const string DEFAULT_SETTINGS_FILE = "UDBuilder.default.cfg"; //mxd
private const string SETTINGS_DIR = "Doom Builder";
private const string LOG_FILE = "UDBuilder.log";
private const string GAME_CONFIGS_DIR = "Configurations";
private const string COMPILERS_DIR = "Compilers";
private const string PLUGINS_DIR = "Plugins";
private const string SCRIPTS_DIR = "Scripting";
private const string SCREENSHOTS_DIR = "Screenshots"; //mxd
private const string SNIPPETS_DIR = "Snippets"; //mxd
private const string MAP_RESTORE_DIR = "Restore"; //mxd
private const string SPRITES_DIR = "Sprites";
private const string TEXTURES_DIR = "Textures"; //mxd
private const string HELP_FILE = "Refmanual.chm";
#endregion
#region ================== Variables
// Files and Folders
private static string apppath;
private static string settingspath;
private static string restorepath; //mxd
private static string logfile;
private static string temppath;
private static string configspath;
private static string compilerspath;
private static string scriptspath;
private static string snippetspath; //mxd
private static string screenshotspath; //mxd
private static string pluginspath;
private static string spritespath;
private static string texturespath; //mxd
// Main objects
private static Assembly thisasm;
private static MainForm mainwindow;
private static ProgramConfiguration settings;
private static MapManager map;
private static EditingManager editing;
private static ActionManager actions;
private static HintsManager hints; //mxd
private static PluginManager plugins;
private static ColorCollection colors;
private static TypesManager types;
private static ErrorLogger errorlogger;
private static string commithash; //mxd. Git commit hash
//private static Mutex appmutex;
// Configurations
private static List<ConfigurationInfo> configs;
private static List<CompilerInfo> compilers;
private static List<NodebuilderInfo> nodebuilders;
private static Dictionary<string, ScriptConfiguration> scriptconfigs;
private static Dictionary<string, ScriptConfiguration> compiledscriptconfigs; //mxd
// States
private static bool debugbuild;
// Command line arguments
private static string[] cmdargs;
private static string autoloadfile;
private static string autoloadmap;
private static string autoloadconfig;
private static bool autoloadstrictpatches;
private static DataLocationList autoloadresources;
private static bool delaymainwindow;
private static bool nosettings;
private static bool portablemode; //mxd
//misc
private static readonly Random random = new Random(); //mxd
#endregion
#region ================== Properties
public static Assembly ThisAssembly { get { return thisasm; } }
public static string AppPath { get { return apppath; } }
public static string TempPath { get { return temppath; } }
public static string ConfigsPath { get { return configspath; } }
internal static string SettingsPath { get { return settingspath; } } //mxd
internal static string MapRestorePath { get { return restorepath; } } //mxd
internal static string LogFile { get { return logfile; } } //mxd
public static string CompilersPath { get { return compilerspath; } }
public static string PluginsPath { get { return pluginspath; } }
public static string SpritesPath { get { return spritespath; } }
internal static string TexturesPath { get { return texturespath; } } //mxd
public static string SnippetsPath { get { return snippetspath; } } //mxd
public static string DefaultScreenshotsPath { get { return screenshotspath; } } //mxd
public static ICollection<string> CommandArgs { get { return Array.AsReadOnly(cmdargs); } }
internal static MainForm MainWindow { get { return mainwindow; } }
public static IMainForm Interface { get { return mainwindow; } }
public static ProgramConfiguration Settings { get { return settings; } }
public static ColorCollection Colors { get { return colors; } }
internal static List<ConfigurationInfo> Configs { get { return configs; } }
internal static List<NodebuilderInfo> Nodebuilders { get { return nodebuilders; } }
internal static List<CompilerInfo> Compilers { get { return compilers; } }
internal static Dictionary<string, ScriptConfiguration> ScriptConfigs { get { return scriptconfigs; } }
internal static Dictionary<string, ScriptConfiguration> CompiledScriptConfigs { get { return compiledscriptconfigs; } } //mxd
public static MapManager Map { get { return map; } }
public static ActionManager Actions { get { return actions; } }
public static HintsManager Hints { get { return hints; } } //mxd
internal static PluginManager Plugins { get { return plugins; } }
public static bool DebugBuild { get { return debugbuild; } }
internal static TypesManager Types { get { return types; } }
public static string AutoLoadFile { get { return autoloadfile; } }
public static string AutoLoadMap { get { return autoloadmap; } }
public static string AutoLoadConfig { get { return autoloadconfig; } }
public static bool AutoLoadStrictPatches { get { return autoloadstrictpatches; } }
public static DataLocationList AutoLoadResources { get { return new DataLocationList(autoloadresources); } }
public static bool DelayMainWindow { get { return delaymainwindow; } }
public static bool NoSettings { get { return nosettings; } }
public static EditingManager Editing { get { return editing; } }
public static ErrorLogger ErrorLogger { get { return errorlogger; } }
public static string CommitHash { get { return commithash; } } //mxd
#endregion
#region ================== Configurations
// This returns the game configuration info by filename
internal static ConfigurationInfo GetConfigurationInfo(string filename)
{
// Go for all config infos
foreach(ConfigurationInfo ci in configs)
{
// Check if filename matches
if(string.Compare(Path.GetFileNameWithoutExtension(ci.Filename),
Path.GetFileNameWithoutExtension(filename), true) == 0)
{
// Return this info
return ci;
}
}
// None found
return null;
}
// This loads and returns a game configuration
private static Configuration LoadGameConfiguration(string filename)
{
// Make the full filepathname
string filepathname = Path.Combine(configspath, filename);
// Load configuration
try
{
// Try loading the configuration
Configuration cfg = new Configuration(filepathname, true);
// Check for erors
if(cfg.ErrorResult)
{
// Error in configuration
errorlogger.Add(ErrorType.Error, "Unable to load the game configuration file \"" + filename + "\". " +
"Error in file \"" + cfg.ErrorFile + "\" near line " + cfg.ErrorLine + ": " + cfg.ErrorDescription);
return null;
}
// Check if this is a Doom Builder 2 config
if(cfg.ReadSetting("type", "") != "Doom Builder 2 Game Configuration")
{
// Old configuration
errorlogger.Add(ErrorType.Error, "Unable to load the game configuration file \"" + filename + "\". " +
"This configuration is not a Doom Builder 2 game configuration.");
return null;
}
// Return config
return cfg;
}
catch(Exception e)
{
// Unable to load configuration
errorlogger.Add(ErrorType.Error, "Unable to load the game configuration file \"" + filename + "\". " + e.GetType().Name + ": " + e.Message);
General.WriteLog(e.StackTrace);
return null;
}
}
// This loads all game configurations
private static void LoadAllGameConfigurations()
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Loading game configurations...");
// Make array
configs = new List<ConfigurationInfo>();
// Go for all cfg files in the configurations directory
string[] filenames = Directory.GetFiles(configspath, "*.cfg", SearchOption.TopDirectoryOnly);
foreach(string filepath in filenames)
{
// Check if it can be loaded
Configuration cfg = LoadGameConfiguration(Path.GetFileName(filepath));
if(cfg != null)
{
string fullfilename = Path.GetFileName(filepath);
ConfigurationInfo cfginfo = new ConfigurationInfo(cfg, fullfilename);
// Add to lists
General.WriteLogLine("Registered game configuration \"" + cfginfo.Name + "\" from \"" + fullfilename + "\"");
configs.Add(cfginfo);
}
}
// Sort the configs
configs.Sort();
}
// This loads all nodebuilder configurations
private static void LoadAllNodebuilderConfigurations()
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Loading nodebuilder configurations...");
// Make array
nodebuilders = new List<NodebuilderInfo>();
// Go for all cfg files in the compilers directory
string[] filenames = Directory.GetFiles(compilerspath, "*.cfg", SearchOption.AllDirectories);
foreach(string filepath in filenames)
{
try
{
// Try loading the configuration
Configuration cfg = new Configuration(filepath, true);
// Check for erors
if(cfg.ErrorResult)
{
// Error in configuration
errorlogger.Add(ErrorType.Error, "Unable to load the compiler configuration file \"" + Path.GetFileName(filepath) + "\". " +
"Error in file \"" + cfg.ErrorFile + "\" near line " + cfg.ErrorLine + ": " + cfg.ErrorDescription);
}
else
{
// Get structures
IDictionary builderslist = cfg.ReadSetting("nodebuilders", new Hashtable());
foreach(DictionaryEntry de in builderslist)
{
// Check if this is a structure
if(de.Value is IDictionary)
{
try
{
// Make nodebuilder info
nodebuilders.Add(new NodebuilderInfo(Path.GetFileName(filepath), de.Key.ToString(), cfg));
}
catch(Exception e)
{
// Unable to load configuration
errorlogger.Add(ErrorType.Error, "Unable to load the nodebuilder configuration \"" + de.Key + "\" from \"" + Path.GetFileName(filepath) + "\". Error: " + e.Message);
}
}
}
}
}
catch(Exception)
{
// Unable to load configuration
errorlogger.Add(ErrorType.Error, "Unable to load the compiler configuration file \"" + Path.GetFileName(filepath) + "\".");
}
}
// Sort the list
nodebuilders.Sort();
}
// This loads all script configurations
private static void LoadAllScriptConfigurations()
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Loading script configurations...");
// Make collection
scriptconfigs = new Dictionary<string, ScriptConfiguration>(StringComparer.Ordinal);
compiledscriptconfigs = new Dictionary<string, ScriptConfiguration>(StringComparer.Ordinal); //mxd
// Go for all cfg files in the scripts directory
string[] filenames = Directory.GetFiles(scriptspath, "*.cfg", SearchOption.TopDirectoryOnly);
foreach(string filepath in filenames)
{
try
{
// Try loading the configuration
Configuration cfg = new Configuration(filepath, true);
// Check for erors
if(cfg.ErrorResult)
{
// Error in configuration
errorlogger.Add(ErrorType.Error, "Unable to load the script configuration file \"" + Path.GetFileName(filepath) + "\". " +
"Error in file \"" + cfg.ErrorFile + "\" near line " + cfg.ErrorLine + ": " + cfg.ErrorDescription);
}
else
{
try
{
// Make script configuration
ScriptConfiguration scfg = new ScriptConfiguration(cfg);
string filename = Path.GetFileName(filepath);
scriptconfigs.Add(filename.ToLowerInvariant(), scfg);
//mxd. Store acc compilers in a separate dictionary
if(scfg.ScriptType == ScriptType.ACS)
compiledscriptconfigs.Add(filename.ToLowerInvariant(), scfg);
}
catch(Exception e)
{
// Unable to load configuration
errorlogger.Add(ErrorType.Error, "Unable to load the script configuration \"" + Path.GetFileName(filepath) + "\". Error: " + e.Message);
}
}
}
catch(Exception e)
{
// Unable to load configuration
errorlogger.Add(ErrorType.Error, "Unable to load the script configuration file \"" + Path.GetFileName(filepath) + "\". Error: " + e.Message);
General.WriteLogLine(e.StackTrace);
}
}
}
// This loads all compiler configurations
private static void LoadAllCompilerConfigurations()
{
Dictionary<string, CompilerInfo> addedcompilers = new Dictionary<string, CompilerInfo>(StringComparer.Ordinal);
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Loading compiler configurations...");
// Make array
compilers = new List<CompilerInfo>();
// Go for all cfg files in the compilers directory
string[] filenames = Directory.GetFiles(compilerspath, "*.cfg", SearchOption.AllDirectories);
foreach(string filepath in filenames)
{
try
{
// Try loading the configuration
Configuration cfg = new Configuration(filepath, true);
// Check for erors
if(cfg.ErrorResult)
{
// Error in configuration
errorlogger.Add(ErrorType.Error, "Unable to load the compiler configuration file \"" + Path.GetFileName(filepath) + "\". " +
"Error in file \"" + cfg.ErrorFile + "\" near line " + cfg.ErrorLine + ": " + cfg.ErrorDescription);
}
else
{
// Get structures
IDictionary compilerslist = cfg.ReadSetting("compilers", new Hashtable());
foreach(DictionaryEntry de in compilerslist)
{
// Check if this is a structure
if(de.Value is IDictionary)
{
// Make compiler info
CompilerInfo info = new CompilerInfo(Path.GetFileName(filepath), de.Key.ToString(), Path.GetDirectoryName(filepath), cfg);
if(!addedcompilers.ContainsKey(info.Name))
{
compilers.Add(info);
addedcompilers.Add(info.Name, info);
}
else
{
errorlogger.Add(ErrorType.Error, "Compiler \"" + info.Name + "\" is defined more than once. The first definition in " + addedcompilers[info.Name].FileName + " will be used.");
}
}
}
}
}
catch(Exception e)
{
// Unable to load configuration
errorlogger.Add(ErrorType.Error, "Unable to load the compiler configuration file \"" + Path.GetFileName(filepath) + "\". " + e.GetType().Name + ": " + e.Message);
General.WriteLogLine(e.StackTrace);
}
}
}
// This returns a nodebuilder by name
internal static NodebuilderInfo GetNodebuilderByName(string name)
{
// Go for all nodebuilders
foreach(NodebuilderInfo n in nodebuilders)
{
// Name matches?
if(n.Name == name) return n;
}
// Cannot find that nodebuilder
return null;
}
/// <summary>
/// Saves the program's configuration
/// </summary>
internal static void SaveSettings()
{
// Save settings configuration
if (!General.NoSettings)
General.Settings.Save(Path.Combine(settingspath, SETTINGS_FILE));
}
/// <summary>
/// Saves the game configuration settings, like engine, resources etc.
/// </summary>
internal static void SaveGameSettings()
{
// Save game configuration settings
if (configs != null) foreach (ConfigurationInfo ci in configs) ci.SaveSettings();
}
#endregion
#region ================== Startup
// Main program entry
[STAThread]
internal static void Main(string[] args)
{
// Determine states
#if DEBUG
debugbuild = true;
#else
debugbuild = false;
//mxd. Custom exception dialog.
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
Application.ThreadException += Application_ThreadException;
#endif
// Enable OS visual styles
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); //mxd
//Application.DoEvents(); // This must be here to work around a .NET bug
//mxd. Set CultureInfo
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
// Set current thread name
Thread.CurrentThread.Name = "Main Application";
// Application is running
//appmutex = new Mutex(false, "gzdoombuilder"); //"doombuilder2"
// Get a reference to this assembly
thisasm = Assembly.GetExecutingAssembly();
// Find application path
apppath = Path.GetDirectoryName(Application.ExecutablePath); //mxd. What was the point of using Uri here (other than to prevent lauching from a shared folder)?
// Parse command-line arguments
ParseCommandLineArgs(args);
// Setup directories
temppath = Path.GetTempPath();
settingspath = (portablemode ? apppath : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), SETTINGS_DIR)); //mxd
restorepath = Path.Combine(settingspath, MAP_RESTORE_DIR);
configspath = Path.Combine(apppath, GAME_CONFIGS_DIR);
compilerspath = Path.Combine(apppath, COMPILERS_DIR);
pluginspath = Path.Combine(apppath, PLUGINS_DIR);
scriptspath = Path.Combine(apppath, SCRIPTS_DIR);
snippetspath = Path.Combine(apppath, SNIPPETS_DIR); //mxd
screenshotspath = Path.Combine(apppath, SCREENSHOTS_DIR).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); //mxd
spritespath = Path.Combine(apppath, SPRITES_DIR);
texturespath = Path.Combine(apppath, TEXTURES_DIR); //mxd
logfile = Path.Combine(settingspath, LOG_FILE);
// Make program settings directory if missing
if(!portablemode && !Directory.Exists(settingspath)) Directory.CreateDirectory(settingspath);
//mxd. Get git commit hash
var hashes = (AssemblyHashAttribute[])thisasm.GetCustomAttributes(typeof(AssemblyHashAttribute), false);
if(hashes.Length == 1)
{
commithash = hashes[0].CommitHash;
}
else
{
WriteLogLine("Unable to determine commit hash. Missing AssemblyHashAttribute?");
commithash = "0000000";
}
// Remove the previous log file and start logging
if(File.Exists(logfile)) File.Delete(logfile);
string platform = Environment.Is64BitProcess ? "x64" : "x86";
General.WriteLogLine("Ultimate Doom Builder R" + thisasm.GetName().Version.Revision + " (" + platform + ", " + commithash + ") startup"); //mxd
General.WriteLogLine("Application path: \"" + apppath + "\"");
General.WriteLogLine("Temporary path: \"" + temppath + "\"");
General.WriteLogLine("Local settings path: \"" + settingspath + "\"");
General.WriteLogLine("Command-line arguments: \"" + string.Join(" ", args) + "\""); //mxd
// Load configuration
General.WriteLogLine("Loading program configuration...");
settings = new ProgramConfiguration();
string defaultsettingsfile = Path.Combine(apppath, DEFAULT_SETTINGS_FILE);
string usersettingsfile = nosettings ? defaultsettingsfile : Path.Combine(settingspath, SETTINGS_FILE);
string legacysettingsfile = nosettings ? String.Empty : Path.Combine(settingspath, LEGACY_SETTINGS_FILE);
if(settings.Load(usersettingsfile, defaultsettingsfile, legacysettingsfile))
{
// Create error logger
errorlogger = new ErrorLogger();
// Create action manager
actions = new ActionManager();
// Bind static methods to actions
General.Actions.BindMethods(typeof(General));
//mxd. Create hints manager
hints = new HintsManager();
// Initialize static classes
MapSet.Initialize();
// Create main window
General.WriteLogLine("Loading main interface window...");
mainwindow = new MainForm();
mainwindow.SetupInterface();
mainwindow.UpdateInterface();
mainwindow.UpdateThingsFilters();
if(!delaymainwindow)
{
// Show main window
General.WriteLogLine("Showing main interface window...");
mainwindow.Show();
mainwindow.Update();
}
// Load plugin manager
General.WriteLogLine("Loading plugins...");
plugins = new PluginManager();
plugins.LoadAllPlugins();
// Load game configurations
General.WriteLogLine("Loading game configurations...");
LoadAllGameConfigurations();
// Create editing modes
General.WriteLogLine("Creating editing modes manager...");
editing = new EditingManager();
// Now that all settings have been combined (core & plugins) apply the defaults
General.WriteLogLine("Applying configuration settings...");
actions.ApplyDefaultShortcutKeys();
mainwindow.ApplyShortcutKeys();
foreach(ConfigurationInfo info in configs) info.ApplyDefaults(null);
// Load compiler configurations
General.WriteLogLine("Loading compiler configurations...");
LoadAllCompilerConfigurations();
// Load nodebuilder configurations
General.WriteLogLine("Loading nodebuilder configurations...");
LoadAllNodebuilderConfigurations();
// Load script configurations
General.WriteLogLine("Loading script configurations...");
LoadAllScriptConfigurations();
// Load color settings
General.WriteLogLine("Loading color settings...");
colors = new ColorCollection(settings.Config);
// Create types manager
General.WriteLogLine("Creating types manager...");
types = new TypesManager();
// Do auto map loading when window is delayed
if(delaymainwindow) mainwindow.PerformAutoMapLoading();
// All done
General.WriteLogLine("Startup done");
mainwindow.DisplayReady();
// Show any errors if preferred
if(errorlogger.IsErrorAdded)
{
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during program startup!");
if(!delaymainwindow && General.Settings.ShowErrorsWindow) mainwindow.ShowErrors();
}
//mxd. Check enabled game configuration
bool noneenabled = true;
for(int i = 0; i < configs.Count; i++)
{
if(configs[i].Enabled)
{
noneenabled = false;
break;
}
}
if(noneenabled)
{
if(MessageBox.Show("No game configurations are currently enabled.\nPlease enable at least one game configuration", "Warning", MessageBoxButtons.OK) == DialogResult.OK)
mainwindow.ShowConfiguration();
}
//mxd. Check backup files
if(Directory.Exists(restorepath))
{
foreach(string backup in Directory.GetFiles(restorepath, "*.restore"))
{
// Remove if created more than a month ago
if((DateTime.Now - File.GetLastWriteTime(backup)).TotalDays > 30)
{
File.Delete(backup);
WriteLogLine("Removed \"" + backup + "\" map backup.");
}
}
}
//mxd. Check for updates?
if(General.Settings.CheckForUpdates) UpdateChecker.PerformCheck(false);
// Run application from the main window
Application.Run(mainwindow);
}
else
{
// Terminate
Terminate(false);
}
}
// This parses the command line arguments
private static void ParseCommandLineArgs(string[] args)
{
autoloadresources = new DataLocationList();
// Keep a copy
cmdargs = args;
// Make a queue so we can parse the values from left to right
Queue<string> argslist = new Queue<string>(args);
// Parse list
while(argslist.Count > 0)
{
// Get next arg
string curarg = argslist.Dequeue();
// Delay window?
if(string.Compare(curarg, "-DELAYWINDOW", true) == 0)
{
// Delay showing the main window
delaymainwindow = true;
}
// No settings?
else if(string.Compare(curarg, "-NOSETTINGS", true) == 0)
{
// Don't load or save program settings
nosettings = true;
}
// Map name info?
else if(string.Compare(curarg, "-MAP", true) == 0)
{
// Store next arg as map name information
autoloadmap = argslist.Dequeue();
}
// Config name info?
else if((string.Compare(curarg, "-CFG", true) == 0) ||
(string.Compare(curarg, "-CONFIG", true) == 0))
{
// Store next arg as config filename information
autoloadconfig = argslist.Dequeue();
}
// Strict patches rules?
else if(string.Compare(curarg, "-STRICTPATCHES", true) == 0)
{
autoloadstrictpatches = true;
}
//mxd. Portable mode?
else if(string.Compare(curarg, "-PORTABLE", true) == 0)
{
// Can we write stuff to apppath?
try
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if(identity != null)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
DirectorySecurity security = Directory.GetAccessControl(apppath);
AuthorizationRuleCollection authrules = security.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach(FileSystemAccessRule accessrule in authrules)
{
SecurityIdentifier id = accessrule.IdentityReference as SecurityIdentifier;
if(id == null || !principal.IsInRole(id)) continue;
if((FileSystemRights.WriteData & accessrule.FileSystemRights) != FileSystemRights.WriteData) continue;
if(accessrule.AccessControlType == AccessControlType.Allow)
{
portablemode = true;
}
else if(accessrule.AccessControlType == AccessControlType.Deny)
{
//Deny usually overrides any Allow
portablemode = false;
break;
}
}
}
}
catch(Exception) { }
// Warn the user?
if(!portablemode) ShowWarningMessage("Failed to enable portable mode.\nMake sure you have write premission for \"" + apppath + "\" directory.", MessageBoxButtons.OK);
}
// Resource?
else if(string.Compare(curarg, "-RESOURCE", true) == 0)
{
DataLocation dl = new DataLocation();
// Parse resource type
string resourcetype = argslist.Dequeue();
if(string.Compare(resourcetype, "WAD", true) == 0)
dl.type = DataLocation.RESOURCE_WAD;
else if(string.Compare(resourcetype, "DIR", true) == 0)
dl.type = DataLocation.RESOURCE_DIRECTORY;
else if(string.Compare(resourcetype, "PK3", true) == 0)
dl.type = DataLocation.RESOURCE_PK3;
else
{
General.WriteLogLine("Unexpected resource type \"" + resourcetype + "\" in program parameters. Expected \"wad\", \"dir\" or \"pk3\".");
break;
}
// We continue parsing args until an existing filename is found
// all other arguments must be one of the optional keywords.
while(string.IsNullOrEmpty(dl.location))
{
curarg = argslist.Dequeue();
if((string.Compare(curarg, "ROOTTEXTURES", true) == 0) &&
(dl.type == DataLocation.RESOURCE_DIRECTORY))
{
// Load images in the root directory of the resource as textures
dl.option1 = true;
}
else if((string.Compare(curarg, "ROOTFLATS", true) == 0) &&
(dl.type == DataLocation.RESOURCE_DIRECTORY))
{
// Load images in the root directory of the resource as flats
dl.option2 = true;
}
else if((string.Compare(curarg, "STRICTPATCHES", true) == 0) &&
(dl.type == DataLocation.RESOURCE_WAD))
{
// Use strict rules for patches
dl.option1 = true;
}
else if(string.Compare(curarg, "NOTEST", true) == 0)
{
// Exclude this resource from testing parameters
dl.notfortesting = true;
}
else
{
// This must be an existing file, or it is an invalid argument
if(dl.type == DataLocation.RESOURCE_DIRECTORY)
{
if(Directory.Exists(curarg))
dl.location = curarg;
}
else
{
if(File.Exists(curarg))
dl.location = curarg;
}
if(string.IsNullOrEmpty(dl.location))
{
General.WriteLogLine("Unexpected argument \"" + curarg + "\" in program parameters. Expected a valid resource option or a resource filename.");
break;
}
}
}
// Add resource to list
if(!string.IsNullOrEmpty(dl.location))
autoloadresources.Add(dl);
}
// Every other arg
else
{
// No command to load file yet?
if(autoloadfile == null)
{
// Check if this is a file we can load
if(File.Exists(curarg))
{
// Load this file!
autoloadfile = curarg.Trim();
}
else
{
// Note in the log that we cannot find this file
General.WriteLogLine("Cannot find the specified file \"" + curarg + "\"");
}
}
}
}
}
// This cancels automatic map loading
internal static void CancelAutoMapLoad()
{
autoloadfile = null;
}
#endregion
#region ================== Terminate
// This is for plugins to use
public static void Exit(bool properexit)
{
// Plugin wants to exit nicely?
if(properexit)
{
// Close dialog forms first
while((Form.ActiveForm != mainwindow) && (Form.ActiveForm != null))
Form.ActiveForm.Close();
// Close main window
mainwindow.Close();
}
else
{
// Terminate, no questions asked
Terminate(true);
}
}
// This terminates the program
internal static void Terminate(bool properexit)
{
// Terminate properly?
if(properexit)
{
General.WriteLogLine("Termination requested");
// Unbind static methods from actions
General.Actions.UnbindMethods(typeof(General));
// Save colors
if(colors != null) colors.SaveColors(settings.Config);
// Save action controls
actions.SaveSettings();
// Save game settings
SaveGameSettings();
// Save program configuration
SaveSettings();
// Clean up
if(map != null) { map.Dispose(); map = null; }
if(editing != null) { editing.Dispose(); editing = null; }
if(plugins != null) { plugins.Dispose(); plugins = null; }
if(mainwindow != null) { mainwindow.Dispose(); mainwindow = null; }
if(actions != null) { actions.Dispose(); actions = null; }
if(types != null) { types.Dispose(); types = null; }
// Application ends here and now
General.WriteLogLine("Termination done");
Application.Exit();
}
else
{
// Just end now
General.WriteLogLine("Immediate program termination");
Application.Exit();
}
// Die.
Process.GetCurrentProcess().Kill();
}
#endregion
#region ================== Management
// This creates a new map
[BeginAction("newmap")]
internal static void NewMap()
{
MapOptions newoptions = new MapOptions();
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Ask the user to save changes (if any)
if(AskSaveMap())
{
// Open map options dialog
MapOptionsForm optionswindow = new MapOptionsForm(newoptions, true);
if(optionswindow.ShowDialog(mainwindow) == DialogResult.OK)
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Creating new map...");
Cursor.Current = Cursors.WaitCursor;
// Clear the display
mainwindow.ClearDisplay();
mainwindow.RemoveHintsDocker(); //mxd
// Trash the current map, if any
if(map != null) map.Dispose();
// Let the plugins know
plugins.OnMapNewBegin();
// Clear old errors (mxd)
errorlogger.Clear();
// Create map manager with given options
map = new MapManager();
if(map.InitializeNewMap(newoptions))
{
settings.FindDefaultDrawSettings(); //mxd
// Let the plugins know
plugins.OnMapNewEnd();
// All done
mainwindow.SetupInterface();
mainwindow.RedrawDisplay();
mainwindow.UpdateThingsFilters();
mainwindow.UpdateLinedefColorPresets(); //mxd
mainwindow.UpdateInterface();
mainwindow.AddHintsDocker(); //mxd
mainwindow.UpdateGZDoomPanel(); //mxd
mainwindow.HideInfo(); //mxd
}
else
{
// Unable to create map manager
map.Dispose();
map = null;
// Show splash logo on display
mainwindow.ShowSplashDisplay();
}
if(errorlogger.IsErrorAdded)
{
// Show any errors if preferred
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during loading!");
if(!delaymainwindow && settings.ShowErrorsWindow) mainwindow.ShowErrors();
}
else
mainwindow.DisplayReady();
//mxd. Also reset the clock...
MainWindow.ResetClock();
Cursor.Current = Cursors.Default;
}
}
}
// This closes the current map
[BeginAction("closemap")]
internal static void ActionCloseMap() { CloseMap(); }
internal static bool CloseMap()
{
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Ask the user to save changes (if any)
if(AskSaveMap())
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Closing map...");
WriteLogLine("Unloading map...");
Cursor.Current = Cursors.WaitCursor;
// Trash the current map
if(map != null) map.Dispose();
map = null;
// Clear errors
errorlogger.Clear();
//mxd. Clear Console
#if DEBUG
DebugConsole.Clear();
#endif
// Show splash logo on display
mainwindow.ShowSplashDisplay();
// Done
Cursor.Current = Cursors.Default;
editing.UpdateCurrentEditModes();
mainwindow.SetupInterface();
mainwindow.RedrawDisplay();
mainwindow.HideInfo();
mainwindow.UpdateThingsFilters();
//mxd
mainwindow.UpdateLinedefColorPresets();
mainwindow.RemoveHintsDocker();
mainwindow.UpdateGZDoomPanel();
mainwindow.UpdateInterface();
mainwindow.DisplayReady();
WriteLogLine("Map unload done");
return true;
}
else
{
// User cancelled
return false;
}
}
// This loads a map from file
[BeginAction("openmap")]
internal static void OpenMap()
{
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Open map file dialog
OpenFileDialog openfile = new OpenFileDialog();
openfile.Title = "Open Map";
#if NO_WIN32
// No easy way to have case-insesitivity for non-Windows platforms
openfile.Filter = "Doom WAD Files (*.wad)|*.wad;*.Wad;*.wAd;*.WAd;*.waD;*.WaD;*.wAD;*.WAD";
#else
openfile.Filter = "Doom WAD Files (*.wad)|*.wad";
#endif
if (!string.IsNullOrEmpty(settings.LastUsedMapFolder) && Directory.Exists(settings.LastUsedMapFolder)) //mxd
{
openfile.RestoreDirectory = true;
openfile.InitialDirectory = settings.LastUsedMapFolder;
}
openfile.AddExtension = false;
openfile.CheckFileExists = true;
openfile.Multiselect = false;
openfile.ValidateNames = true;
if(openfile.ShowDialog(mainwindow) == DialogResult.OK)
{
// Update main window
mainwindow.Update();
// Open map file
OpenMapFile(openfile.FileName, null);
}
openfile.Dispose();
}
//mxd. This loads a different map from same wad file without reloading resources
[BeginAction("openmapincurrentwad")]
internal static void OpenMapInCurrentWad()
{
if(map == null || string.IsNullOrEmpty(map.FilePathName) || !File.Exists(map.FilePathName))
{
Interface.DisplayStatus(StatusType.Warning, "Unable to open map from current WAD!");
return;
}
// Cancel volatile mode, if any
Editing.DisengageVolatileMode();
// Ask the user to save changes (if any)
if(!AskSaveMap()) return;
// Open map options dialog
ChangeMapForm changemapwindow = new ChangeMapForm(map.FilePathName, map.Options);
if(changemapwindow.ShowDialog(mainwindow) != DialogResult.OK) return;
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Switching to map \"" + changemapwindow.Options.CurrentName + "\"...");
WriteLogLine("Switching to map \"" + changemapwindow.Options.CurrentName + "\"...");
Cursor.Current = Cursors.WaitCursor;
// Let the plugins know
plugins.OnMapCloseBegin();
// Clear the display
mainwindow.ClearDisplay();
mainwindow.RemoveHintsDocker(); //mxd
//mxd. Close the script editor
map.CloseScriptEditor(false);
// Let the plugins know
plugins.OnMapCloseEnd();
plugins.OnMapOpenBegin();
// Clear old errors
ErrorLogger.Clear();
if(!map.InitializeSwitchMap(changemapwindow.Options)) return;
// Clear undo history
map.UndoRedo.ClearAllUndos();
map.UndoRedo.ClearAllRedos();
settings.FindDefaultDrawSettings(); //mxd
// Let the plugins know
plugins.OnMapOpenEnd();
// All done
mainwindow.SetupInterface();
mainwindow.RedrawDisplay();
mainwindow.UpdateThingsFilters();
mainwindow.UpdateLinedefColorPresets(); //mxd
mainwindow.UpdateInterface();
mainwindow.HideInfo();
mainwindow.AddHintsDocker(); //mxd
mainwindow.UpdateGZDoomPanel(); //mxd
if(errorlogger.IsErrorAdded)
{
// Show any errors if preferred
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during loading!");
if(!delaymainwindow && Settings.ShowErrorsWindow)
mainwindow.ShowErrors();
}
else
{
mainwindow.DisplayReady();
}
Cursor.Current = Cursors.Default;
}
// This opens the specified file
internal static void OpenMapFile(string filename, MapOptions options)
{
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Ask the user to save changes (if any)
if(AskSaveMap())
{
// Open map options dialog
OpenMapOptionsForm openmapwindow = (options != null ? new OpenMapOptionsForm(filename, options) : new OpenMapOptionsForm(filename));
if(openmapwindow.ShowDialog(mainwindow) == DialogResult.OK)
OpenMapFileWithOptions(filename, openmapwindow.Options);
}
}
// This opens the specified file without dialog
internal static void OpenMapFileWithOptions(string filename, MapOptions options)
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Opening map file...");
Cursor.Current = Cursors.WaitCursor;
// Clear the display
mainwindow.ClearDisplay();
mainwindow.RemoveHintsDocker(); //mxd
// Trash the current map, if any
if(map != null) map.Dispose();
// Let the plugins know
plugins.OnMapOpenBegin();
// mxd. Clear old errors
errorlogger.Clear();
// Create map manager with given options
map = new MapManager();
if(map.InitializeOpenMap(filename, options))
{
// Add recent file
mainwindow.AddRecentFile(filename);
//mxd
mainwindow.UpdateGZDoomPanel();
settings.LastUsedMapFolder = Path.GetDirectoryName(filename);
settings.FindDefaultDrawSettings();
// Let the plugins know
plugins.OnMapOpenEnd();
// All done
mainwindow.SetupInterface();
mainwindow.UpdateThingsFilters();
mainwindow.UpdateLinedefColorPresets(); //mxd
mainwindow.UpdateInterface();
mainwindow.HideInfo();
mainwindow.AddHintsDocker(); //mxd
//mxd. Center map in screen or on stored coordinates. Done here to avoid the view jerking around when updating the interface.
ClassicMode mode = Editing.Mode as ClassicMode;
if(mode != null)
{
if(options != null && options.ViewPosition.IsFinite() && !float.IsNaN(options.ViewScale))
mode.CenterOnCoordinates(options.ViewPosition, options.ViewScale);
else
mode.CenterInScreen();
}
mainwindow.RedrawDisplay();
}
else
{
// Unable to create map manager
map.Dispose();
map = null;
// Show splash logo on display
mainwindow.ShowSplashDisplay();
}
if(errorlogger.IsErrorAdded)
{
// Show any errors if preferred
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during loading!");
if(!delaymainwindow && settings.ShowErrorsWindow) mainwindow.ShowErrors();
}
else
mainwindow.DisplayReady();
Cursor.Current = Cursors.Default;
}
// This saves the current map
// Returns tre when saved, false when cancelled or failed
[BeginAction("savemap")]
internal static void ActionSaveMap() { SaveMap(); }
internal static bool SaveMap()
{
if(map == null) return false;
bool result = false;
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Check if a wad file is known
if(string.IsNullOrEmpty(map.FilePathName))
{
// Call to SaveMapAs
result = SaveMapAs();
}
else
{
//mxd. Do we need to save the map?
if(!map.MapSaveRequired(map.FilePathName, SavePurpose.Normal))
{
// Still save settings file
result = map.SaveSettingsFile(map.FilePathName);
// Display status
mainwindow.DisplayStatus(StatusType.Info, "Map is up to date. Updated map settings file.");
// All done
mainwindow.UpdateInterface();
return result;
}
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Saving map file...");
Cursor.Current = Cursors.WaitCursor;
// Set this to false so we can see if errors are added
errorlogger.IsErrorAdded = false;
// Save the map
plugins.OnMapSaveBegin(SavePurpose.Normal);
if(map.SaveMap(map.FilePathName, SavePurpose.Normal))
{
// Add recent file
mainwindow.AddRecentFile(map.FilePathName);
result = true;
}
plugins.OnMapSaveEnd(SavePurpose.Normal);
// All done
mainwindow.UpdateInterface();
if(errorlogger.IsErrorAdded)
{
// Show any errors if preferred
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during saving!");
if(!delaymainwindow && settings.ShowErrorsWindow) mainwindow.ShowErrors();
}
else if(result)
{
mainwindow.DisplayStatus(StatusType.Info, "Map saved in " + map.FileTitle + ".");
}
else
{
mainwindow.DisplayStatus(StatusType.Info, "Map saving cancelled."); //mxd
}
Cursor.Current = Cursors.Default;
}
//mxd. Also reset the clock...
MainWindow.ResetClock();
return result;
}
// This saves the current map as a different file
// Returns tre when saved, false when cancelled or failed
[BeginAction("savemapas")]
internal static void ActionSaveMapAs() { SaveMapAs(); }
internal static bool SaveMapAs()
{
if(map == null) return false;
bool result = false;
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Show save as dialog
SaveFileDialog savefile = new SaveFileDialog();
savefile.Filter = "Doom WAD Files (*.wad)|*.wad";
savefile.Title = "Save Map As";
savefile.AddExtension = true;
savefile.CheckPathExists = true;
savefile.OverwritePrompt = true;
savefile.ValidateNames = true;
savefile.FileName = map.FileTitle; //mxd
if(map.FilePathName.Length > 0) //mxd
{
savefile.RestoreDirectory = true;
savefile.InitialDirectory = Path.GetDirectoryName(map.FilePathName);
}
if(savefile.ShowDialog(mainwindow) == DialogResult.OK)
{
// Check if we're saving to the same file as the original.
// Because some muppets use Save As even when saving to the same file.
string currentfilename = (map.FilePathName.Length > 0) ? Path.GetFullPath(map.FilePathName).ToLowerInvariant() : "";
string savefilename = Path.GetFullPath(savefile.FileName).ToLowerInvariant();
if(currentfilename == savefilename)
{
SaveMap();
}
else
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Saving map file...");
Cursor.Current = Cursors.WaitCursor;
// Set this to false so we can see if errors are added
errorlogger.IsErrorAdded = false;
// Save the map
plugins.OnMapSaveBegin(SavePurpose.AsNewFile);
if(map.SaveMap(savefile.FileName, SavePurpose.AsNewFile))
{
// Add recent file
mainwindow.AddRecentFile(map.FilePathName);
settings.LastUsedMapFolder = Path.GetDirectoryName(map.FilePathName); //mxd
result = true;
}
plugins.OnMapSaveEnd(SavePurpose.AsNewFile);
// All done
mainwindow.UpdateInterface();
if(errorlogger.IsErrorAdded)
{
// Show any errors if preferred
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during saving!");
if(!delaymainwindow && settings.ShowErrorsWindow) mainwindow.ShowErrors();
}
else if(result)
{
mainwindow.DisplayStatus(StatusType.Info, "Map saved in " + map.FileTitle + ".");
}
else
{
mainwindow.DisplayStatus(StatusType.Info, "Map saving cancelled."); //mxd
}
Cursor.Current = Cursors.Default;
}
}
savefile.Dispose();
//mxd. Also reset the clock...
MainWindow.ResetClock();
return result;
}
// This saves the current map as a different file
// Returns tre when saved, false when cancelled or failed
[BeginAction("savemapinto")]
internal static void ActionSaveMapInto() { SaveMapInto(); }
internal static bool SaveMapInto()
{
if(map == null) return false;
bool result = false;
// Cancel volatile mode, if any
editing.DisengageVolatileMode();
// Show save as dialog
SaveFileDialog savefile = new SaveFileDialog();
savefile.Filter = "Doom WAD Files (*.wad)|*.wad";
savefile.Title = "Save Map Into";
savefile.AddExtension = true;
savefile.CheckPathExists = true;
savefile.OverwritePrompt = false;
savefile.ValidateNames = true;
if(savefile.ShowDialog(mainwindow) == DialogResult.OK)
{
// Display status
mainwindow.DisplayStatus(StatusType.Busy, "Saving map file...");
Cursor.Current = Cursors.WaitCursor;
// Set this to false so we can see if errors are added
errorlogger.IsErrorAdded = false;
// Save the map
plugins.OnMapSaveBegin(SavePurpose.IntoFile);
if(map.SaveMap(savefile.FileName, SavePurpose.IntoFile))
{
// Add recent file
mainwindow.AddRecentFile(map.FilePathName);
result = true;
}
plugins.OnMapSaveEnd(SavePurpose.IntoFile);
// All done
mainwindow.UpdateInterface();
if(errorlogger.IsErrorAdded)
{
// Show any errors if preferred
mainwindow.DisplayStatus(StatusType.Warning, "There were errors during saving!");
if(!delaymainwindow && settings.ShowErrorsWindow) mainwindow.ShowErrors();
}
else if(result)
{
mainwindow.DisplayStatus(StatusType.Info, "Map saved in " + map.FileTitle + ".");
}
else
{
mainwindow.DisplayStatus(StatusType.Info, "Map saving cancelled."); //mxd
}
Cursor.Current = Cursors.Default;
}
savefile.Dispose();
//mxd. Also reset the clock...
MainWindow.ResetClock();
return result;
}
// This asks to save the map if needed
// Returns false when action was cancelled
internal static bool AskSaveMap()
{
// Map open and not saved?
if(map != null)
{
if(map.IsChanged)
{
// Ask to save changes
DialogResult result = MessageBox.Show(mainwindow, "Do you want to save changes to " + map.FileTitle + " (" + map.Options.CurrentName + ")?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if(result == DialogResult.Yes)
{
// Save map
if(SaveMap())
{
// Ask to save changes to scripts
return map.AskSaveScriptChanges();
}
else
{
// Failed to save map
return false;
}
}
else if(result == DialogResult.Cancel)
{
// Abort
return false;
}
else
{
// Ask to save changes to scripts
return map.AskSaveScriptChanges();
}
}
else
{
// Ask to save changes to scripts
return map.AskSaveScriptChanges();
}
}
else
{
return true;
}
}
#endregion
#region ================== Debug
// This shows a major failure
public static void Fail(string message)
{
WriteLogLine("FAIL: " + message);
#if DEBUG
Debug.Fail(message);
#else
//mxd. Lets notify the user about our Epic Failure before crashing...
ShowErrorMessage(message, MessageBoxButtons.OK);
#endif
Terminate(false);
}
// This outputs log information
public static void WriteLogLine(string line)
{
#if DEBUG
// Output to consoles
Console.WriteLine(line);
DebugConsole.WriteLine(DebugMessageType.LOG, line); //mxd
#endif
// Write to log file
try { File.AppendAllText(logfile, line + Environment.NewLine); }
catch(Exception) { }
}
// This outputs log information
public static void WriteLog(string text)
{
#if DEBUG
// Output to consoles
Console.Write(text);
DebugConsole.Write(DebugMessageType.LOG, text);
#endif
// Write to log file
try { File.AppendAllText(logfile, text); }
catch(Exception) { }
}
#endregion
#region ================== Tools
// This swaps two pointers
public static void Swap<T>(ref T a, ref T b)
{
T t = a;
a = b;
b = t;
}
// This calculates the bits needed for a number
public static int BitsForInt(int v)
{
int[] LOGTABLE = new[] {
0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 };
int r; // r will be lg(v)
int t, tt;
if(Int2Bool(tt = v >> 16))
{
r = Int2Bool(t = tt >> 8) ? 24 + LOGTABLE[t] : 16 + LOGTABLE[tt];
}
else
{
r = Int2Bool(t = v >> 8) ? 8 + LOGTABLE[t] : LOGTABLE[v];
}
return r;
}
// This clamps a value
public static float Clamp(float value, float min, float max)
{
return Math.Min(Math.Max(min, value), max);
}
// This clamps a value
public static double Clamp(double value, double min, double max)
{
return Math.Min(Math.Max(min, value), max);
}
// This clamps a value
public static int Clamp(int value, int min, int max)
{
return Math.Min(Math.Max(min, value), max);
}
// This clamps a value
public static byte Clamp(byte value, byte min, byte max)
{
return Math.Min(Math.Max(min, value), max);
}
//mxd. This clamps angle between 0 and 359
public static int ClampAngle(int angle)
{
angle %= 360;
if(angle < 0) angle += 360;
return angle;
}
//mxd. This clamps angle between 0 and 359
public static float ClampAngle(float angle)
{
angle %= 360;
if(angle < 0) angle += 360;
return angle;
}
// This clamps angle between 0 and 359
public static double ClampAngle(double angle)
{
angle %= 360;
if (angle < 0) angle += 360;
return angle;
}
//mxd
public static int Random(int min, int max)
{
return random.Next(min, max + 1); //because max is never rolled
}
//mxd
public static float Random(float min, float max)
{
return (float)Math.Round(min + (max - min) * random.NextDouble(), 2);
}
public static double Random(double min, double max)
{
return Math.Round(min + (max - min) * random.NextDouble(), 2);
}
// This returns an element from a collection by index
public static T GetByIndex<T>(ICollection<T> collection, int index)
{
IEnumerator<T> e = collection.GetEnumerator();
for(int i = -1; i < index; i++) e.MoveNext();
return e.Current;
}
// This returns the next power of 2
/*public static int NextPowerOf2(int v)
{
int p = 0;
// Continue increasing until higher than v
while(Math.Pow(2, p) < v) p++;
// Return power
return (int)Math.Pow(2, p);
}*/
//mxd. This returns the next power of 2. Taken from http://bits.stephan-brumme.com/roundUpToNextPowerOfTwo.html
public static int NextPowerOf2(int x)
{
x--;
x |= x >> 1; // handle 2 bit numbers
x |= x >> 2; // handle 4 bit numbers
x |= x >> 4; // handle 8 bit numbers
x |= x >> 8; // handle 16 bit numbers
x |= x >> 16; // handle 32 bit numbers
x++;
return x;
}
// Convert bool to integer
internal static int Bool2Int(bool v)
{
return v ? 1 : 0;
}
// Convert integer to bool
internal static bool Int2Bool(int v)
{
return (v != 0);
}
// This shows a message and logs the message
public static DialogResult ShowErrorMessage(string message, MessageBoxButtons buttons)
{
return ShowErrorMessage(message, buttons, true);
}
// This shows a message and logs the message
public static DialogResult ShowErrorMessage(string message, MessageBoxButtons buttons, bool log)
{
//mxd. Log the message?
if(log) WriteLogLine(message);
// Use normal cursor
Cursor oldcursor = Cursor.Current;
Cursor.Current = Cursors.Default;
// Show message
IWin32Window window = null;
if((Form.ActiveForm != null) && Form.ActiveForm.Visible) window = Form.ActiveForm;
DialogResult result = MessageBox.Show(window, message, Application.ProductName, buttons, MessageBoxIcon.Error);
// Restore old cursor
Cursor.Current = oldcursor;
// Return result
return result;
}
// This shows a message and logs the message
public static DialogResult ShowWarningMessage(string message, MessageBoxButtons buttons)
{
return ShowWarningMessage(message, buttons, MessageBoxDefaultButton.Button1, true);
}
// This shows a message and logs the message
public static DialogResult ShowWarningMessage(string message, MessageBoxButtons buttons, MessageBoxDefaultButton defaultbutton)
{
return ShowWarningMessage(message, buttons, defaultbutton, true);
}
// This shows a message and logs the message
public static DialogResult ShowWarningMessage(string message, MessageBoxButtons buttons, MessageBoxDefaultButton defaultbutton, bool log)
{
//mxd. Log the message?
if(log) WriteLogLine(message);
// Use normal cursor
Cursor oldcursor = Cursor.Current;
Cursor.Current = Cursors.Default;
// Show message
IWin32Window window = null;
if((Form.ActiveForm != null) && Form.ActiveForm.Visible) window = Form.ActiveForm;
DialogResult result = MessageBox.Show(window, message, Application.ProductName, buttons, MessageBoxIcon.Warning, defaultbutton);
// Restore old cursor
Cursor.Current = oldcursor;
// Return result
return result;
}
// This shows the reference manual
public static void ShowHelp(string pagefile)
{
ShowHelp(pagefile, HELP_FILE);
}
// This shows the reference manual
public static void ShowHelp(string pagefile, string chmfile)
{
// Check if the file can be found in the root
string filepathname = Path.Combine(apppath, chmfile);
if(!File.Exists(filepathname))
{
// Check if the file exists in the plugins directory
filepathname = Path.Combine(pluginspath, chmfile);
if(!File.Exists(filepathname))
{
// Fail
WriteLogLine("ERROR: Can't find the help file \"" + chmfile + "\"");
return;
}
}
// Show help file
Help.ShowHelp(mainwindow, filepathname, HelpNavigator.Topic, pagefile);
}
// This returns a unique temp filename
internal static string MakeTempFilename(string tempdir)
{
return MakeTempFilename(tempdir, "tmp");
}
// This returns a unique temp filename
internal static string MakeTempFilename(string tempdir, string extension)
{
string filename;
const string chars = "abcdefghijklmnopqrstuvwxyz1234567890";
do
{
// Generate a filename
filename = "";
for(int i = 0; i < 8; i++) filename += chars[Random(0, chars.Length - 1)];
filename = Path.Combine(tempdir, filename + "." + extension);
}
// Continue while file is not unique
while(File.Exists(filename) || Directory.Exists(filename));
// Return the filename
return filename;
}
// This returns a unique temp directory name
internal static string MakeTempDirname()
{
string dirname;
const string chars = "abcdefghijklmnopqrstuvwxyz1234567890";
do
{
// Generate a filename
dirname = "";
for(int i = 0; i < 8; i++) dirname += chars[Random(0, chars.Length - 1)];
dirname = Path.Combine(temppath, dirname);
}
// Continue while file is not unique
while(File.Exists(dirname) || Directory.Exists(dirname));
// Return the filename
return dirname;
}
// This shows an image in a panel either zoomed or centered depending on size
public static void DisplayZoomedImage(Panel panel, Image image)
{
// Image not null?
if(image != null)
{
// Set the image
panel.BackgroundImage = image;
// Display zoomed
panel.BackgroundImageLayout = ImageLayout.Zoom;
}
}
// This calculates the new rectangle when one is scaled into another keeping aspect ratio
public static RectangleF MakeZoomedRect(Size source, RectangleF target)
{
return MakeZoomedRect(new SizeF(source.Width, source.Height), target);
}
// This calculates the new rectangle when one is scaled into another keeping aspect ratio
public static RectangleF MakeZoomedRect(Size source, Rectangle target)
{
return MakeZoomedRect(new SizeF(source.Width, source.Height),
new RectangleF(target.Left, target.Top, target.Width, target.Height));
}
// This calculates the new rectangle when one is scaled into another keeping aspect ratio
public static RectangleF MakeZoomedRect(SizeF source, RectangleF target)
{
float scale;
// Image fits?
if((source.Width <= target.Width) && (source.Height <= target.Height))
{
// Just center
scale = 1.0f;
}
// Image is wider than tall?
else if((source.Width - target.Width) > (source.Height - target.Height))
{
// Scale down by width
scale = target.Width / source.Width;
}
else
{
// Scale down by height
scale = target.Height / source.Height;
}
// Return centered and scaled
return new RectangleF(target.Left + (target.Width - source.Width * scale) * 0.5f,
target.Top + (target.Height - source.Height * scale) * 0.5f,
source.Width * scale, source.Height * scale);
}
// This opens a URL in the default browser
public static void OpenWebsite(string url)
{
// [ZZ] note: it may break. no idea why it was done like it was done.
string url2 = url.ToLowerInvariant();
if (!url2.StartsWith("http://") && !url2.StartsWith("https://") && !url2.StartsWith("ftp://") && !url2.StartsWith("mailto:"))
return;
System.Diagnostics.Process.Start(url);
/*
RegistryKey key = null;
Process p = null;
string browser;
try
{
// Get the registry key where default browser is stored
key = Registry.ClassesRoot.OpenSubKey(@"HTTP\shell\open\command", false);
// Trim off quotes
browser = key.GetValue(null).ToString().ToLower().Replace("\"", "");
// String doesnt end in EXE?
if(!browser.EndsWith("exe"))
{
// Get rid of everything after the ".exe"
browser = browser.Substring(0, browser.LastIndexOf(".exe") + 4);
}
}
finally
{
// Clean up
if(key != null) key.Close();
}
try
{
// Fork a process
p = new Process();
p.StartInfo.FileName = browser;
p.StartInfo.Arguments = url;
p.Start();
}
catch(Exception) { }
// Clean up
if(p != null) p.Dispose();*/
}
// This returns the short path name for a file
public static string GetShortFilePath(string longpath)
{
#if NO_WIN32
return longpath;
#else
const int maxlen = 256;
StringBuilder shortname = new StringBuilder(maxlen);
GetShortPathName(longpath, shortname, maxlen);
return shortname.ToString();
#endif
}
public static string GetLinuxFilePath(string longpath)
{
string linuxpath;
linuxpath = longpath.Replace('\\', '/');
string wineprefix = Environment.GetEnvironmentVariable("WINEPREFIX");
if (linuxpath.Substring(0, 2) == "C:")
{
linuxpath = wineprefix + "/drive_c" + linuxpath.Substring(2);
}
else if (linuxpath.Substring(0,2) == "Z:")
{
linuxpath = linuxpath.Substring(2);
}
return linuxpath;
}
//mxd
internal static ScriptConfiguration GetScriptConfiguration(ScriptType type)
{
if(type == ScriptType.ACS)
{
// Return map-defined compiler
string compiler = (!string.IsNullOrEmpty(Map.Options.ScriptCompiler) ? Map.Options.ScriptCompiler : Map.ConfigSettings.DefaultScriptCompiler);
foreach(KeyValuePair<string, ScriptConfiguration> group in scriptconfigs)
{
if(group.Key == compiler) return group.Value;
}
}
else
{
// Just pick the first one from the list
foreach(ScriptConfiguration cfg in scriptconfigs.Values)
{
if(cfg.ScriptType == type) return cfg;
}
}
return null;
}
//mxd
public static bool CheckWritePremissions(string path)
{
try
{
string testFile = path + "/GZDBWriteTest.tmp";
if (File.Exists(testFile))
File.Delete(testFile);
FileStream fs = File.OpenWrite(testFile);
fs.Close();
File.Delete(testFile);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region ================== mxd. Uncaught exceptions handling
// In some cases the program can remain operational after these
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
try
{
// Try handling it in user-friendy way...
ExceptionDialog dlg = new ExceptionDialog(e);
dlg.Setup();
if(dlg.ShowDialog() == DialogResult.Cancel) Terminate(false);
}
catch
{
string exceptionmsg;
// Try getting exception details...
try { exceptionmsg = "Fatal Windows Forms error occurred: " + e.Exception.Message + "\n\nStack Trace:\n" + e.Exception.StackTrace; }
catch(Exception exc) { exceptionmsg = "Failed to get initial exception details: " + exc.Message + "\n\nStack Trace:\n" + exc.StackTrace; }
// Try logging it...
try { WriteLogLine(exceptionmsg); } catch { }
// Try displaying it to the user...
try { MessageBox.Show(exceptionmsg, "Fatal Windows Forms Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); }
finally { Process.GetCurrentProcess().Kill(); }
}
}
// These are usually unrecoverable
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
try
{
// Try handling it in user-friendy way...
ExceptionDialog dlg = new ExceptionDialog(e);
dlg.Setup();
if(dlg.ShowDialog() == DialogResult.Cancel) Terminate(false);
}
catch
{
string exceptionmsg;
// Try getting exception details...
try
{
Exception ex = (Exception)e.ExceptionObject;
exceptionmsg = "Fatal Non-UI error:\n" + ex.Message + "\n\nStack Trace:\n" + ex.StackTrace;
}
catch(Exception exc)
{
exceptionmsg = "Failed to get initial exception details:\n" + exc.Message + "\n\nStack Trace:\n" + exc.StackTrace;
}
// Try logging it...
try { WriteLogLine(exceptionmsg); } catch {}
// Try displaying it to the user...
try { MessageBox.Show(exceptionmsg, "Fatal Non-UI Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); }
finally { Process.GetCurrentProcess().Kill(); }
}
}
#endregion
}
}