UltimateZoneBuilder/Source/Core/General/General.cs
MaxED 3d2d9e21ef Changed, Classic modes: displayed mouse map position coordinates are now snapped to current grid size.
Updated model pitch handling to match current GZDoom implementation.
Fixed imprecise vertex coordinates generated by Draw Ellipse mode.
Fixed a resource loading exception when opened map file wad was located in the root of a Directory resource.
Internal: changed output of all InterpolationTools methods from int to float.
Fixed, Internal: InterpolationTools.InterpolateColor() delta usage was inverted.
2016-06-03 20:22:07 +00:00

2175 lines
68 KiB
C#

#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;
using Microsoft.Win32;
using SlimDX.Direct3D9;
#endregion
namespace CodeImp.DoomBuilder
{
public static class General
{
#region ================== API Declarations
[DllImport("devil.dll")]
private static extern void ilInit();
[DllImport("user32.dll")]
internal static extern bool LockWindowUpdate(IntPtr hwnd);
[DllImport("kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)]
internal static extern void ZeroMemory(IntPtr dest, int size);
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
internal static extern unsafe void CopyMemory(void* dst, void* src, uint length);
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern int SendMessage(IntPtr hwnd, uint Msg, int wParam, int lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MessageBeep(MessageBeepType type);
//[DllImport("kernel32.dll")]
//internal extern static IntPtr LoadLibrary(string filename);
//[DllImport("kernel32.dll")]
//internal extern static bool FreeLibrary(IntPtr moduleptr);
//[DllImport("user32.dll")]
/*internal static extern IntPtr CreateWindowEx(uint exstyle, string classname, string windowname, uint style,
int x, int y, int width, int height, IntPtr parentptr, int menu,
IntPtr instanceptr, string param);*/
//[DllImport("user32.dll")]
//internal static extern bool DestroyWindow(IntPtr windowptr);
//[DllImport("user32.dll")]
//internal static extern int SetWindowPos(IntPtr windowptr, int insertafterptr, int x, int y, int cx, int cy, int flags);
[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);
//[DllImport("user32.dll")]
//internal static extern int SetScrollInfo(IntPtr windowptr, int bar, IntPtr scrollinfo, bool redraw);
//[DllImport("user32.dll")]
//internal static extern int GetScrollInfo(IntPtr windowptr, int bar, IntPtr scrollinfo);
#endregion
#region ================== Constants
// SendMessage API
internal const int WM_USER = 0x400;
internal const int WM_SYSCOMMAND = 0x112;
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 SETTINGS_FILE = "GZBuilder.cfg";
private const string DEFAULT_SETTINGS_FILE = "GZBuilder.default.cfg"; //mxd
private const string SETTINGS_DIR = "Doom Builder";
private const string LOG_FILE = "GZBuilder.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 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; } }
internal static int PendingUpdateRev; //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;
}
#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;
// Hook to DLL loading failure event
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
// 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
//Uri localpath = new Uri(Path.GetDirectoryName(thisasm.GetName().CodeBase));
//apppath = Uri.UnescapeDataString(localpath.AbsolutePath);
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);
// Remove the previous log file and start logging
if(File.Exists(logfile)) File.Delete(logfile);
General.WriteLogLine("GZDoom Builder R" + thisasm.GetName().Version.Revision + " 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);
if(settings.Load(usersettingsfile, defaultsettingsfile))
{
// 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();
ilInit();
// 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();
}
// Start Direct3D
General.WriteLogLine("Starting Direct3D graphics driver...");
try { D3DDevice.Startup(); }
catch(Direct3D9NotFoundException e) { AskDownloadDirectX(e.Message); return; }
catch(Direct3DX9NotFoundException e) { AskDownloadDirectX(e.Message); return; }
// 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 handles DLL linking errors
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// Check if SlimDX failed loading
if(args.Name.Contains("SlimDX")) AskDownloadSlimDX();
// Return null
return null;
}
// This asks the user to download DirectX
private static void AskDownloadDirectX(string message)
{
// Cancel loading map from command-line parameters, if any.
// This causes problems, because when the window is shown, the map will
// be loaded and DirectX is initialized (which we seem to be missing)
CancelAutoMapLoad();
// Ask the user to download DirectX
if(MessageBox.Show("Unable to initialize DirectX: " + message + Environment.NewLine + Environment.NewLine +
"Do you want to install and/or update Microsoft DirectX 9.0 now?", "DirectX 9.0 Error", MessageBoxButtons.YesNo,
MessageBoxIcon.Exclamation) == DialogResult.Yes)
{
// Go to DirectX End-User Runtime Web Installer page (mxd)
OpenWebsite("https://www.microsoft.com/en-us/download/details.aspx?id=35&44F86079-8679-400C-BFF2-9CA5F2BCBDFC=1");
}
// End program here
Terminate(false);
}
// This asks the user to download SlimDX (mxd)
private static void AskDownloadSlimDX()
{
// Cancel loading map from command-line parameters, if any.
// This causes problems, because when the window is shown, the map will
// be loaded and SlimDX is initialized (which we seem to be missing)
CancelAutoMapLoad();
// Ask the user to download SlimDX
if(MessageBox.Show("This application requires the latest version of SlimDX for .NET 2.0 installed on your computer." + Environment.NewLine +
"Do you want to install SlimDX now?", "SlimDX Error", MessageBoxButtons.YesNo,
MessageBoxIcon.Exclamation) == DialogResult.Yes)
{
// Go to SlimDX download page
OpenWebsite("http://slimdx.org/download.php");
}
// End program here
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(PendingUpdateRev != 0 ? "Program update requested" : "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 configuration settings
if(configs != null) foreach(ConfigurationInfo ci in configs) ci.SaveSettings();
// Save settings configuration
if(!General.NoSettings)
settings.Save(Path.Combine(settingspath, SETTINGS_FILE));
// 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; }
try { D3DDevice.Terminate(); } catch { }
//mxd. Launch the updater?
if(PendingUpdateRev != 0)
{
General.WriteLogLine("Initiating update to R" + PendingUpdateRev + "...");
Process.Start(Path.Combine(apppath, "Updater.exe"), "-rev " + PendingUpdateRev);
}
// 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();
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.Filter = "Doom WAD Files (*.wad)|*.wad";
openfile.Title = "Open Map";
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;
}
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();
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();
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 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)
{
return (angle + 360) % 360;
}
//mxd. This clamps angle between 0 and 359
public static float ClampAngle(float angle)
{
return (angle + 360) % 360;
}
//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);
}
// 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)
{
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)
{
const int maxlen = 256;
StringBuilder shortname = new StringBuilder(maxlen);
GetShortPathName(longpath, shortname, maxlen);
return shortname.ToString();
}
//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;
}
#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("Fatal Windows Forms Error", exceptionmsg, 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("Fatal Windows Forms Error", exceptionmsg, MessageBoxButtons.OK, MessageBoxIcon.Stop); }
finally { Process.GetCurrentProcess().Kill(); }
}
}
#endregion
}
}