UltimateZoneBuilder/Source/Core/General/MapManager.cs
MaxED fe38d59169 Added, Classic modes: default thing arguments are now taken into account when highlighting related map elements.
Probably fixed an exception when trying to check which program(s) lock the map file on Windows XP.
Changed: added an option to try to close all programs, which lock the map file when saving the map and the map file is locked (requires Windows Vista or newer).
Fixed, Preferences window: some trackbars had incorrect background color.
Fixed, General interface: in some cases status messages were almost immediately replaced by selection info messages.
2015-09-08 14:54:53 +00:00

2279 lines
No EOL
72 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.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Compilers;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.GZBuilder.Data; //mxd
using CodeImp.DoomBuilder.GZBuilder.GZDoom; //mxd
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.VisualModes;
using CodeImp.DoomBuilder.Windows;
#endregion
namespace CodeImp.DoomBuilder
{
public sealed class MapManager
{
#region ================== Constants
// Map header name in temporary file
internal const string TEMP_MAP_HEADER = "TEMPMAP";
internal const string BUILD_MAP_HEADER = "MAP01";
public const string CONFIG_MAP_HEADER = "~MAP";
#endregion
#region ================== Variables
// Status
private bool changed;
private bool scriptschanged;
// Map information
private string filetitle;
private string filepathname;
private string temppath;
private string origmapconfigname; //mxd. Map configuration, which was used to open the map.
// Main objects
private MapSet map;
private MapSetIO io;
private MapOptions options;
private ConfigurationInfo configinfo;
private GameConfiguration config;
private DataManager data;
private D3DDevice graphics;
private Renderer2D renderer2d;
private Renderer3D renderer3d;
private WAD tempwad;
private GridSetup grid;
private UndoManager undoredo;
private CopyPasteManager copypaste;
private Launcher launcher;
private ThingsFilter thingsfilter;
private ScriptEditorForm scriptwindow;
private List<CompilerError> errors;
private VisualCamera visualcamera;
//mxd
private Dictionary<string, ScriptItem> namedscripts;
private Dictionary<int, ScriptItem> numberedscripts;
private List<string> scriptincludes;
// Disposing
private bool isdisposed;
#endregion
#region ================== Properties
public string FilePathName { get { return filepathname; } }
public string FileTitle { get { return filetitle; } internal set { filetitle = value; } } //mxd. Added setter
public string TempPath { get { return temppath; } }
public MapOptions Options { get { return options; } }
public MapSet Map { get { return map; } }
public DataManager Data { get { return data; } }
public bool IsChanged { get { return changed | CheckScriptChanged(); } set { changed |= value; } }
public bool IsDisposed { get { return isdisposed; } }
internal D3DDevice Graphics { get { return graphics; } }
public IRenderer2D Renderer2D { get { return renderer2d; } }
public IRenderer3D Renderer3D { get { return renderer3d; } }
internal Renderer2D CRenderer2D { get { return renderer2d; } }
internal Renderer3D CRenderer3D { get { return renderer3d; } }
public GameConfiguration Config { get { return config; } }
public ConfigurationInfo ConfigSettings { get { return configinfo; } }
public GridSetup Grid { get { return grid; } }
public UndoManager UndoRedo { get { return undoredo; } }
internal CopyPasteManager CopyPaste { get { return copypaste; } }
public IMapSetIO FormatInterface { get { return io; } }
internal Launcher Launcher { get { return launcher; } }
public ThingsFilter ThingsFilter { get { return thingsfilter; } }
internal List<CompilerError> Errors { get { return errors; } }
internal ScriptEditorForm ScriptEditor { get { return scriptwindow; } }
public VisualCamera VisualCamera { get { return visualcamera; } set { visualcamera = value; } }
public bool IsScriptsWindowOpen { get { return (scriptwindow != null) && !scriptwindow.IsDisposed; } }
//mxd
public bool UDMF { get { return config.FormatInterface == "UniversalMapSetIO"; } }
internal Dictionary<string, ScriptItem> NamedScripts { get { return namedscripts; } }
internal Dictionary<int, ScriptItem> NumberedScripts { get { return numberedscripts; } }
internal List<string> ScriptIncludes { get { return scriptincludes; } }
public ViewMode ViewMode { get { return renderer2d.ViewMode; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
internal MapManager()
{
// We have no destructor
GC.SuppressFinalize(this);
// Create temporary path
temppath = General.MakeTempDirname();
Directory.CreateDirectory(temppath);
General.WriteLogLine("Temporary directory: " + temppath);
// Basic objects
grid = new GridSetup();
undoredo = new UndoManager();
copypaste = new CopyPasteManager();
launcher = new Launcher(this);
thingsfilter = new NullThingsFilter();
errors = new List<CompilerError>();
//mxd
numberedscripts = new Dictionary<int, ScriptItem>();
namedscripts = new Dictionary<string, ScriptItem>();
scriptincludes = new List<string>();
}
// Disposer
internal bool Dispose()
{
// Not already disposed?
if (!isdisposed)
{
// Let the plugins know
General.Plugins.OnMapCloseBegin();
// Stop processing
General.MainWindow.StopProcessing();
// Close script editor
CloseScriptEditor(false);
// Change to no mode
General.Editing.ChangeMode((EditMode)null);
// Unbind any methods
General.Actions.UnbindMethods(this);
// Dispose
if (grid != null) grid.Dispose();
if (launcher != null) launcher.Dispose();
if (copypaste != null) copypaste.Dispose();
if (undoredo != null) undoredo.Dispose();
General.WriteLogLine("Unloading data resources...");
if (data != null) data.Dispose();
General.WriteLogLine("Closing temporary file...");
if (tempwad != null) tempwad.Dispose();
General.WriteLogLine("Unloading map data...");
if (map != null) map.Dispose();
General.WriteLogLine("Stopping graphics device...");
if (renderer2d != null) renderer2d.Dispose();
if (renderer3d != null) renderer3d.Dispose();
if (graphics != null) graphics.Dispose();
visualcamera = null;
grid = null;
launcher = null;
copypaste = null;
undoredo = null;
data = null;
tempwad = null;
map = null;
renderer2d = null;
renderer3d = null;
graphics = null;
// We may spend some time to clean things up here
GC.Collect();
GC.WaitForPendingFinalizers(); //mxd
GC.Collect(); //mxd
// Remove temp file
General.WriteLogLine("Removing temporary directory...");
try
{
Directory.Delete(temppath, true);
}
catch (Exception e)
{
General.WriteLogLine(e.GetType().Name + ": " + e.Message);
General.WriteLogLine("Failed to remove temporary directory!");
}
// Let the plugins know
General.Plugins.OnMapCloseEnd();
// Done
isdisposed = true;
return true;
}
// Already closed
return true;
}
#endregion
#region ================== New / Open
// Initializes for a new map
internal bool InitializeNewMap(MapOptions options)
{
#if DEBUG
DebugConsole.Clear();
#endif
// Apply settings
this.filetitle = options.CurrentName + ".wad";
this.filepathname = "";
this.changed = false;
this.options = options;
General.WriteLogLine("Creating new map '" + options.CurrentName + "' with configuration '" + options.ConfigFile + "'");
// Initiate graphics
General.WriteLogLine("Initializing graphics device...");
graphics = new D3DDevice(General.MainWindow.Display);
if (!graphics.Initialize()) return false;
// Create renderers
renderer2d = new Renderer2D(graphics);
renderer3d = new Renderer3D(graphics);
// Load game configuration
General.WriteLogLine("Loading game configuration...");
configinfo = General.GetConfigurationInfo(options.ConfigFile);
config = new GameConfiguration(configinfo.Configuration); //mxd
origmapconfigname = configinfo.Filename;//mxd
configinfo.ApplyDefaults(config);
General.Editing.UpdateCurrentEditModes();
// Create map data
map = new MapSet();
// Create temp wadfile
string tempfile = General.MakeTempFilename(temppath);
General.WriteLogLine("Creating temporary file: " + tempfile);
#if DEBUG
tempwad = new WAD(tempfile);
#else
try { tempwad = new WAD(tempfile); }
catch(Exception e)
{
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
// Read the map from temp file
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
// Create required lumps
General.WriteLogLine("Creating map data structures...");
tempwad.Insert(TEMP_MAP_HEADER, 0, 0);
io.Write(map, TEMP_MAP_HEADER, 1);
CreateRequiredLumps(tempwad, TEMP_MAP_HEADER);
// Load data manager
General.WriteLogLine("Loading data resources...");
data = new DataManager();
data.Load(configinfo.Resources, options.Resources);
// Update structures
options.ApplyGridSettings();
map.UpdateConfiguration();
map.Update();
thingsfilter.Update();
namedscripts = new Dictionary<string, ScriptItem>(); //mxd
numberedscripts = new Dictionary<int, ScriptItem>(); //mxd
// Bind any methods
General.Actions.BindMethods(this);
// Set defaults
this.visualcamera = new VisualCamera();
General.Editing.ChangeMode(configinfo.StartMode);
ClassicMode cmode = (General.Editing.Mode as ClassicMode);
if (cmode != null) cmode.SetZoom(0.5f);
renderer2d.SetViewMode((ViewMode)General.Settings.DefaultViewMode);
General.Settings.SetDefaultThingFlags(config.DefaultThingFlags);
// Success
this.changed = false;
General.WriteLogLine("Map creation done");
return true;
}
// Initializes for an existing map
internal bool InitializeOpenMap(string filepathname, MapOptions options)
{
WAD mapwad;
string tempfile;
#if DEBUG
DebugConsole.Clear();
#endif
// Apply settings
this.filetitle = Path.GetFileName(filepathname);
this.filepathname = filepathname;
this.changed = false;
this.options = options;
General.WriteLogLine("Opening map '" + options.CurrentName + "' with configuration '" + options.ConfigFile + "'");
// Initiate graphics
General.WriteLogLine("Initializing graphics device...");
graphics = new D3DDevice(General.MainWindow.Display);
if (!graphics.Initialize()) return false;
// Create renderers
renderer2d = new Renderer2D(graphics);
renderer3d = new Renderer3D(graphics);
// Load game configuration
General.WriteLogLine("Loading game configuration...");
configinfo = General.GetConfigurationInfo(options.ConfigFile);
config = new GameConfiguration(configinfo.Configuration);
origmapconfigname = configinfo.Filename;//mxd
configinfo.ApplyDefaults(config);
General.Editing.UpdateCurrentEditModes();
// Create map data
map = new MapSet();
// Create temp wadfile
tempfile = General.MakeTempFilename(temppath);
General.WriteLogLine("Creating temporary file: " + tempfile);
#if DEBUG
tempwad = new WAD(tempfile);
#else
try { tempwad = new WAD(tempfile); } catch(Exception e)
{
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
// Now open the map file
General.WriteLogLine("Opening source file: " + filepathname);
#if DEBUG
mapwad = new WAD(filepathname, true);
#else
try { mapwad = new WAD(filepathname, true); } catch(Exception e)
{
General.ShowErrorMessage("Error while opening source wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
// Copy the map lumps to the temp file
General.WriteLogLine("Copying map lumps to temporary file...");
CopyLumpsByType(mapwad, options.CurrentName, tempwad, TEMP_MAP_HEADER,
true, true, true, true);
// Close the map file
mapwad.Dispose();
// Read the map from temp file
map.BeginAddRemove();
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
General.WriteLogLine("Reading map data structures from file...");
#if DEBUG
map = io.Read(map, TEMP_MAP_HEADER);
#else
try { map = io.Read(map, TEMP_MAP_HEADER); } catch(Exception e)
{
General.ErrorLogger.Add(ErrorType.Error, "Unable to read the map data structures with the specified configuration. " + e.GetType().Name + ": " + e.Message);
General.ShowErrorMessage("Unable to read the map data structures with the specified configuration.", MessageBoxButtons.OK);
return false;
}
#endif
map.EndAddRemove();
// Load data manager
General.WriteLogLine("Loading data resources...");
data = new DataManager();
DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false);
data.Load(configinfo.Resources, options.Resources, maplocation);
// Remove unused sectors
map.RemoveUnusedSectors(true);
//mxd. Translate to long or short texture names.
bool nameschanged = map.TranslateTextureNames(config.UseLongTextureNames, false);
grid.TranslateBackgroundName(config.UseLongTextureNames);
//mxd. Sector textures may've been changed
if (nameschanged) data.UpdateUsedTextures();
// Update structures
options.ApplyGridSettings();
map.UpdateConfiguration();
map.SnapAllToAccuracy();
map.Update();
thingsfilter.Update();
//mxd. check script names
UpdateScriptNames();
//mxd. Restore selection groups
options.ReadSelectionGroups();
// Bind any methods
General.Actions.BindMethods(this);
// Set defaults
this.visualcamera = new VisualCamera();
General.Editing.ChangeMode(configinfo.StartMode);
renderer2d.SetViewMode((ViewMode)General.Settings.DefaultViewMode);
General.Settings.SetDefaultThingFlags(config.DefaultThingFlags);
// Center map in screen
//if (General.Editing.Mode is ClassicMode) (General.Editing.Mode as ClassicMode).CenterInScreen();
// Success
this.changed = false;
General.WriteLogLine("Map loading done");
return true;
}
//mxd. This switches to another map in the same wad
internal bool InitializeSwitchMap(MapOptions options)
{
#if DEBUG
DebugConsole.Clear();
#endif
this.changed = false;
this.options = options;
// Create map data
MapSet newmap = new MapSet();
WAD mapwad;
// Create temp wadfile
string tempfile = General.MakeTempFilename(temppath);
General.WriteLogLine("Creating temporary file: " + tempfile);
if(tempwad != null) tempwad.Dispose();
#if DEBUG
tempwad = new WAD(tempfile);
#else
try { tempwad = new WAD(tempfile); } catch(Exception e)
{
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
// Now open the map file
General.WriteLogLine("Opening source file: " + filepathname);
#if DEBUG
mapwad = new WAD(filepathname, true);
#else
try { mapwad = new WAD(filepathname, true); } catch(Exception e)
{
General.ShowErrorMessage("Error while opening source wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
// Copy the map lumps to the temp file
General.WriteLogLine("Copying map lumps to temporary file...");
CopyLumpsByType(mapwad, options.CurrentName, tempwad, TEMP_MAP_HEADER, true, true, true, true);
// Close the map file
mapwad.Dispose();
// Read the map from temp file
newmap.BeginAddRemove();
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
General.WriteLogLine("Reading map data structures from file...");
#if DEBUG
newmap = io.Read(newmap, TEMP_MAP_HEADER);
#else
try { newmap = io.Read(newmap, TEMP_MAP_HEADER); } catch(Exception e)
{
General.ErrorLogger.Add(ErrorType.Error, "Unable to read the map data structures with the specified configuration. " + e.GetType().Name + ": " + e.Message);
General.ShowErrorMessage("Unable to read the map data structures with the specified configuration.", MessageBoxButtons.OK);
return false;
}
#endif
newmap.EndAddRemove();
ChangeMapSet(newmap);
//mxd. Translate texture names
map.TranslateTextureNames(config.UseLongTextureNames, false);
grid.TranslateBackgroundName(config.UseLongTextureNames);
//mxd. Sector textures may've been changed
data.UpdateUsedTextures();
//mxd. check script names
UpdateScriptNames();
//mxd. Restore selection groups
options.ReadSelectionGroups();
//mxd. Center map in screen or on stored coordinates
if (General.Editing.Mode is ClassicMode)
{
ClassicMode mode = General.Editing.Mode as ClassicMode;
mode.OnRedoEnd();
if(options.ViewPosition.IsFinite() && !float.IsNaN(options.ViewScale))
mode.CenterOnCoordinates(options.ViewPosition, options.ViewScale);
else
mode.CenterInScreen();
}
// Success
this.changed = false;
General.WriteLogLine("Map switching done");
return true;
}
#endregion
#region ================== Save
/// <summary>
/// This exports the structures from memory into a WAD file with the current map format.
/// </summary>
public bool ExportToFile(string filepathname)
{
return SaveMap(filepathname, SavePurpose.Testing);
}
// Initializes for an existing map
internal bool SaveMap(string newfilepathname, SavePurpose purpose)
{
//mxd. Check if the target file is locked
FileLockChecker.FileLockCheckResult checkresult = FileLockChecker.CheckFile(newfilepathname);
if(!string.IsNullOrEmpty(checkresult.Error))
{
if(checkresult.Processes.Count > 0)
{
string rest = "Press 'Retry' to close " + (checkresult.Processes.Count > 1 ? "all processes" : "the process") + " and retry." + Environment.NewLine + "Press 'Cancel' to cancel saving.";
if(General.ShowErrorMessage(checkresult.Error + rest, MessageBoxButtons.RetryCancel) == DialogResult.Retry)
{
// Close all processes
foreach (Process process in checkresult.Processes)
{
try
{
if(!process.HasExited) process.Kill();
}
catch(Exception e)
{
General.ShowErrorMessage("Failed to close " + Path.GetFileName(process.MainModule.FileName) + ":" + Environment.NewLine + Environment.NewLine + e.Message, MessageBoxButtons.OK);
return false;
}
}
// Retry
return SaveMap(newfilepathname, purpose);
}
else
{
return false;
}
}
else
{
General.ShowErrorMessage(checkresult.Error, MessageBoxButtons.OK);
return false;
}
}
string settingsfile;
WAD targetwad;
int index;
bool includenodes;
General.WriteLogLine("Saving map to file: " + newfilepathname);
// Scripts changed?
bool localscriptschanged = CheckScriptChanged();
// If the scripts window is open, save the scripts first
if (IsScriptsWindowOpen) scriptwindow.Editor.ImplicitSave();
// Only recompile scripts when the scripts have changed
// (not when only the map changed)
if(localscriptschanged && !CompileScriptLumps())
{
// Compiler failure
if(errors.Count > 0)
General.ShowErrorMessage("Error while compiling scripts: " + errors[0].description, MessageBoxButtons.OK);
else
General.ShowErrorMessage("Unknown compiler error while compiling scripts!", MessageBoxButtons.OK);
}
// Show script window if there are any errors and we are going to test the map
// and always update the errors on the scripts window.
if ((errors.Count > 0) && (scriptwindow == null) && (purpose == SavePurpose.Testing)) ShowScriptEditor();
if (scriptwindow != null) scriptwindow.Editor.ShowErrors(errors);
// Only write the map and rebuild nodes when the actual map has changed
// (not when only scripts have changed)
if (changed)
{
// Make a copy of the map data
MapSet outputset = map.Clone();
// Remove all flags from all 3D Start things
foreach (Thing t in outputset.Things)
{
if (t.Type == config.Start3DModeThingType)
{
// We're not using SetFlag here, this doesn't have to be undone.
// Please note that this is totally exceptional!
List<string> flagkeys = new List<string>(t.Flags.Keys);
foreach (string k in flagkeys) t.Flags[k] = false;
}
}
// Do we need sidedefs compression?
StatusInfo oldstatus;
if (map.Sidedefs.Count > io.MaxSidedefs)
{
// Compress sidedefs
oldstatus = General.MainWindow.Status;
General.MainWindow.DisplayStatus(StatusType.Busy, "Compressing sidedefs...");
outputset.CompressSidedefs();
General.MainWindow.DisplayStatus(oldstatus);
// Check if it still doesnt fit
if (outputset.Sidedefs.Count > io.MaxSidedefs)
{
// Problem! Can't save the map like this!
General.ShowErrorMessage("Unable to save the map: There are too many unique sidedefs!", MessageBoxButtons.OK);
return false;
}
}
// Check things
if (map.Things.Count > io.MaxThings)
{
General.ShowErrorMessage("Unable to save the map: There are too many things!", MessageBoxButtons.OK);
return false;
}
// Check sectors
if (map.Sectors.Count > io.MaxSectors)
{
General.ShowErrorMessage("Unable to save the map: There are too many sectors!", MessageBoxButtons.OK);
return false;
}
// Check linedefs
if (map.Linedefs.Count > io.MaxLinedefs)
{
General.ShowErrorMessage("Unable to save the map: There are too many linedefs!", MessageBoxButtons.OK);
return false;
}
// Check vertices
if (map.Vertices.Count > io.MaxVertices)
{
General.ShowErrorMessage("Unable to save the map: There are too many vertices!", MessageBoxButtons.OK);
return false;
}
// TODO: Check for more limitations
// Write to temporary file
General.WriteLogLine("Writing map data structures to file...");
index = tempwad.FindLumpIndex(TEMP_MAP_HEADER);
if (index == -1) index = 0;
io.Write(outputset, TEMP_MAP_HEADER, index);
outputset.Dispose();
// Get the corresponding nodebuilder
string nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave;
// Build the nodes
oldstatus = General.MainWindow.Status;
General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes...");
includenodes = (!string.IsNullOrEmpty(nodebuildername) && BuildNodes(nodebuildername, true));
General.MainWindow.DisplayStatus(oldstatus);
}
else
{
// Check if we have nodebuilder lumps
includenodes = VerifyNodebuilderLumps(tempwad, TEMP_MAP_HEADER);
}
// Suspend data resources
data.Suspend();
// Determine original map name
string origmapname = (options.PreviousName != "" && purpose != SavePurpose.IntoFile) ? options.PreviousName : options.CurrentName;
string origwadfile = string.Empty; //mxd
try
{
if (File.Exists(newfilepathname))
{
// mxd. Check if target wad already has a map with the same name
if (purpose == SavePurpose.IntoFile)
{
WAD wad = new WAD(newfilepathname, true);
int mapindex = wad.FindLumpIndex(origmapname);
wad.Dispose();
if(mapindex != -1 && MessageBox.Show(General.MainWindow, "Target file already contains map '" + origmapname + "'\nDo you want to replace it?", "Map already exists!", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
{
data.Resume();
General.WriteLogLine("Map saving cancelled...");
return false;
}
}
// Backup existing file, if any
if (File.Exists(newfilepathname + ".backup3")) File.Delete(newfilepathname + ".backup3");
if (File.Exists(newfilepathname + ".backup2")) File.Move(newfilepathname + ".backup2", newfilepathname + ".backup3");
if (File.Exists(newfilepathname + ".backup1")) File.Move(newfilepathname + ".backup1", newfilepathname + ".backup2");
File.Copy(newfilepathname, newfilepathname + ".backup1");
}
// Except when saving INTO another file,
// kill the target file if it is different from source file
if ((purpose != SavePurpose.IntoFile) && (newfilepathname != filepathname))
{
// Kill target file
if (File.Exists(newfilepathname)) File.Delete(newfilepathname);
// Kill .dbs settings file
settingsfile = newfilepathname.Substring(0, newfilepathname.Length - 4) + ".dbs";
if (File.Exists(settingsfile)) File.Delete(settingsfile);
}
// On Save AS we have to copy the previous file to the new file
if ((purpose == SavePurpose.AsNewFile) && (filepathname != ""))
{
// Copy if original file still exists
if (File.Exists(filepathname)) File.Copy(filepathname, newfilepathname, true);
}
// If the target file exists, we need to rebuild it
if (File.Exists(newfilepathname))
{
// Move the target file aside
origwadfile = newfilepathname + ".temp";
File.Move(newfilepathname, origwadfile);
// Open original file
WAD origwad = new WAD(origwadfile, true);
// Create new target file
targetwad = new WAD(newfilepathname);
// Copy all lumps, except the original map
GameConfiguration origcfg; //mxd
if (origmapconfigname == configinfo.Filename)
{
origcfg = config;
}
else
{
ConfigurationInfo ci = General.GetConfigurationInfo(origmapconfigname);
origcfg = new GameConfiguration(ci.Configuration);
}
CopyAllLumpsExceptMap(origwad, targetwad, origcfg, origmapname);
// Close original file and delete it
origwad.Dispose();
File.Delete(origwadfile);
}
else
{
// Create new target file
targetwad = new WAD(newfilepathname);
}
}
catch (IOException)
{
General.ShowErrorMessage("IO Error while writing target file: " + newfilepathname + ". Please make sure the location is accessible and not in use by another program.", MessageBoxButtons.OK);
if(!string.IsNullOrEmpty(origwadfile) && File.Exists(origwadfile)) File.Delete(origwadfile); //mxd. Clean-up
data.Resume();
General.WriteLogLine("Map saving failed");
return false;
}
catch (UnauthorizedAccessException)
{
General.ShowErrorMessage("Error while accessing target file: " + newfilepathname + ". Please make sure the location is accessible and not in use by another program.", MessageBoxButtons.OK);
if(!string.IsNullOrEmpty(origwadfile) && File.Exists(origwadfile)) File.Delete(origwadfile); //mxd. Clean-up
data.Resume();
General.WriteLogLine("Map saving failed");
return false;
}
// Copy map lumps to target file
CopyLumpsByType(tempwad, TEMP_MAP_HEADER, targetwad, origmapname, true, true, includenodes, true);
// mxd. Was the map renamed?
if(options.LevelNameChanged)
{
if (purpose != SavePurpose.IntoFile)
{
General.WriteLogLine("Changing map name from '" + options.PreviousName + "' to '" + options.CurrentName + "'");
// Find the map header in target
index = targetwad.FindLumpIndex(options.PreviousName);
if (index > -1)
{
// Rename the map lump name
targetwad.Lumps[index].Rename(options.CurrentName);
}
else
{
// Houston, we've got a problem!
General.ShowErrorMessage("Error renaming map lump name: the original map lump could not be found!", MessageBoxButtons.OK);
options.CurrentName = options.PreviousName;
}
}
options.PreviousName = "";
}
// Done with the target file
targetwad.Dispose();
// Resume data resources
data.Resume();
// Not saved for testing purpose?
if (purpose != SavePurpose.Testing)
{
// Saved in a different file?
if (newfilepathname != filepathname)
{
// Keep new filename
filepathname = newfilepathname;
filetitle = Path.GetFileName(filepathname);
// Reload resources
ReloadResources();
}
try
{
// Open or create the map settings
settingsfile = newfilepathname.Substring(0, newfilepathname.Length - 4) + ".dbs";
options.WriteConfiguration(settingsfile);
}
catch (Exception e)
{
// Warning only
General.ErrorLogger.Add(ErrorType.Warning, "Could not write the map settings configuration file. " + e.GetType().Name + ": " + e.Message);
}
// Changes saved
changed = false;
scriptschanged = false;
}
// Success!
General.WriteLogLine("Map saving done");
return true;
}
//mxd. Don't save the map if it was not changed
internal bool MapSaveRequired(string newfilepathname, SavePurpose purpose)
{
return (changed || scriptschanged || CheckScriptChanged() || options.LevelNameChanged || newfilepathname != filepathname || purpose != SavePurpose.Normal);
}
//mxd. Saves .dbs file
internal bool SaveSettingsFile(string newfilepathname)
{
try
{
string settingsfile = newfilepathname.Substring(0, newfilepathname.Length - 4) + ".dbs";
if(File.Exists(settingsfile)) File.Delete(settingsfile);
options.WriteConfiguration(settingsfile);
}
catch(Exception e)
{
// Warning only
General.ErrorLogger.Add(ErrorType.Warning, "Could not write the map settings configuration file. " + e.GetType().Name + ": " + e.Message);
return false;
}
return true;
}
#endregion
#region ================== Nodebuild
// This builds the nodes in the temproary file with the given configuration name
private bool BuildNodes(string nodebuildername, bool failaswarning)
{
string tempfile2;
bool lumpscomplete = false;
WAD buildwad;
// Find the nodebuilder
NodebuilderInfo nodebuilder = General.GetNodebuilderByName(nodebuildername);
if (nodebuilder == null)
{
// Problem! Can't find that nodebuilder!
General.ShowWarningMessage("Unable to build the nodes: The configured nodebuilder cannot be found.\nPlease check your game configuration settings!", MessageBoxButtons.OK);
return false;
}
else
{
// Create the compiler interface that will run the nodebuilder
// This automatically creates a temporary directory for us
Compiler compiler = nodebuilder.CreateCompiler();
// Make temporary filename
string tempfile1 = General.MakeTempFilename(compiler.Location);
// Make the temporary WAD file
General.WriteLogLine("Creating temporary build file: " + tempfile1);
#if DEBUG
buildwad = new WAD(tempfile1);
#else
try { buildwad = new WAD(tempfile1); }
catch(Exception e)
{
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
// Determine source file
string sourcefile = (filepathname.Length > 0 ? filepathname : tempwad.Filename);
//mxd.
RemoveUnneededLumps(tempwad, TEMP_MAP_HEADER, true);
// Copy lumps to buildwad
General.WriteLogLine("Copying map lumps to temporary build file...");
CopyLumpsByType(tempwad, TEMP_MAP_HEADER, buildwad, BUILD_MAP_HEADER, true, false, false, true);
// Close buildwad
buildwad.Dispose();
// Does the nodebuilder require an output file?
if (nodebuilder.HasSpecialOutputFile)
{
// Make a temporary output file for the nodebuilder
tempfile2 = General.MakeTempFilename(compiler.Location);
General.WriteLogLine("Temporary output file: " + tempfile2);
}
else
{
// Output file is same as input file
tempfile2 = tempfile1;
}
// Run the nodebuilder
compiler.Parameters = nodebuilder.Parameters;
compiler.InputFile = Path.GetFileName(tempfile1);
compiler.OutputFile = Path.GetFileName(tempfile2);
compiler.SourceFile = sourcefile;
compiler.WorkingDirectory = Path.GetDirectoryName(tempfile1);
if (compiler.Run())
{
// Open the output file
try { buildwad = new WAD(tempfile2); }
catch (Exception e)
{
General.WriteLogLine(e.GetType().Name + " while reading build wad file: " + e.Message);
buildwad = null;
}
if (buildwad != null)
{
// Output lumps complete?
lumpscomplete = VerifyNodebuilderLumps(buildwad, BUILD_MAP_HEADER);
}
if (lumpscomplete)
{
// Copy nodebuilder lumps to temp file
General.WriteLogLine("Copying nodebuilder lumps to temporary file...");
CopyLumpsByType(buildwad, BUILD_MAP_HEADER, tempwad, TEMP_MAP_HEADER, false, false, true, false);
}
else
{
//mxd. collect errors
string compilererrors = "";
foreach (CompilerError e in compiler.Errors)
compilererrors += Environment.NewLine + e.description;
// Nodebuilder did not build the lumps!
if (failaswarning)
General.ShowWarningMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures.\nThe map will be saved without the nodes." + (compiler.Errors.Length > 0 ? Environment.NewLine + compilererrors : ""), MessageBoxButtons.OK);
else
General.ShowErrorMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures." + (compiler.Errors.Length > 0 ? Environment.NewLine + compilererrors : ""), MessageBoxButtons.OK);
}
// Done with the build wad
if (buildwad != null) buildwad.Dispose();
}
else //mxd
{
//collect errors
string compilererrors = "";
foreach (CompilerError e in compiler.Errors)
compilererrors += Environment.NewLine + e.description;
// Nodebuilder did not build the lumps!
General.ShowErrorMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures" + (compiler.Errors.Length > 0 ? ":" + Environment.NewLine + compilererrors : "."), MessageBoxButtons.OK);
}
// Clean up
compiler.Dispose();
// Return result
return lumpscomplete;
}
}
// This verifies if the nodebuilder lumps exist in a WAD file
private bool VerifyNodebuilderLumps(WAD wad, string mapheader)
{
bool lumpscomplete = false;
// Find the map header in source
int srcindex = wad.FindLumpIndex(mapheader);
if (srcindex > -1)
{
// Go for all the map lump names
lumpscomplete = true;
foreach (KeyValuePair<string, MapLumpInfo> group in config.MapLumps)
{
// Check if this lump should exist
if(group.Value.NodeBuild && !group.Value.AllowEmpty && group.Value.Required)
{
//mxd
string lumpname = group.Key;
if (lumpname.Contains(CONFIG_MAP_HEADER)) lumpname = lumpname.Replace(CONFIG_MAP_HEADER, mapheader);
// Find the lump in the source
if(wad.FindLump(lumpname, srcindex, srcindex + config.MapLumps.Count + 2) == null)
{
// Missing a lump!
lumpscomplete = false;
break;
}
}
}
}
return lumpscomplete;
}
#endregion
#region ================== Lumps
// This returns a copy of the requested lump stream data
// This is copied from the temp wad file and returns null when the lump is not found
public MemoryStream GetLumpData(string lumpname)
{
Lump l = tempwad.FindLump(lumpname);
if (l != null)
{
l.Stream.Seek(0, SeekOrigin.Begin);
return new MemoryStream(l.Stream.ReadAllBytes());
}
return null;
}
// This writes a copy of the data to a lump in the temp file
public void SetLumpData(string lumpname, MemoryStream data)
{
int insertindex = tempwad.Lumps.Count;
// Remove the lump if it already exists
int li = tempwad.FindLumpIndex(lumpname);
if (li > -1)
{
insertindex = li;
tempwad.RemoveAt(li);
}
// Insert new lump
Lump l = tempwad.Insert(lumpname, insertindex, (int)data.Length);
l.Stream.Seek(0, SeekOrigin.Begin);
data.WriteTo(l.Stream);
}
// This checks if the specified lump exists in the temp file
public bool LumpExists(string lumpname)
{
return (tempwad.FindLumpIndex(lumpname) > -1);
}
// This creates empty lumps for those required
private void CreateRequiredLumps(WAD target, string mapname)
{
// Find the map header in target
int headerindex = target.FindLumpIndex(mapname);
if (headerindex == -1)
{
// If this header doesnt exists in the target
// then insert at the end of the target
headerindex = target.Lumps.Count;
}
// Begin inserting at target header index
int insertindex = headerindex;
// Go for all the map lump names
foreach (KeyValuePair<string, MapLumpInfo> group in config.MapLumps)
{
// Check if this lump is required
if(group.Value.Required)
{
// Get the lump name
string lumpname = (group.Key.Contains(CONFIG_MAP_HEADER) ? group.Key.Replace(CONFIG_MAP_HEADER, mapname) : group.Key); //mxd
// Check if the lump is missing at the target
int targetindex = FindSpecificLump(target, lumpname, headerindex, mapname, config.MapLumps);
if (targetindex == -1)
{
// Determine target index
insertindex++;
if (insertindex > target.Lumps.Count) insertindex = target.Lumps.Count;
// Create new, emtpy lump
General.WriteLogLine(lumpname + " is required! Created empty lump.");
target.Insert(lumpname, insertindex, 0);
}
else
{
// Move insert index
insertindex = targetindex;
}
}
}
}
//mxd. This is called on tempwad, which should only have the current map inside it.
private void RemoveUnneededLumps(WAD target, string mapname, bool glnodesonly)
{
//Get the list of lumps required by current map format
List<string> requiredLumps = new List<string>();
foreach(KeyValuePair<string, MapLumpInfo> group in config.MapLumps)
{
//this lump well be recreated by a nodebuilder when saving the map
//(or it won't be if the new map format or nodebuilder doesn't require / build this lump,
//so it will just stay there, possibly messing things up)
if(group.Value.NodeBuild && (!glnodesonly || group.Key.ToUpperInvariant().StartsWith("GL_"))) continue;
string lumpname = group.Key;
if(lumpname == CONFIG_MAP_HEADER) lumpname = mapname;
requiredLumps.Add(lumpname);
}
//Remove lumps, which are not required
List<Lump> toRemove = new List<Lump>();
foreach (Lump lump in target.Lumps)
if (!requiredLumps.Contains(lump.Name)) toRemove.Add(lump);
foreach (Lump lump in toRemove) target.Remove(lump);
}
// This copies all lumps, except those of a specific map
private static void CopyAllLumpsExceptMap(WAD source, WAD target, GameConfiguration mapconfig, string sourcemapname)
{
// Go for all lumps
bool skipping = false;
foreach (Lump srclump in source.Lumps)
{
// Check if we should stop skipping lumps here
if (skipping)
{
//mxd
string srclumpname = srclump.Name;
if (srclumpname.Contains(sourcemapname)) srclumpname = srclumpname.Replace(sourcemapname, CONFIG_MAP_HEADER);
if (!mapconfig.MapLumps.ContainsKey(srclumpname))
{
// Stop skipping
skipping = false;
}
}
// Check if we should start skipping lumps here
if (!skipping && (srclump.Name == sourcemapname))
{
// We have encountered the map header, start skipping!
skipping = true;
}
// Not skipping this lump?
if (!skipping)
{
// Copy lump over!
Lump tgtlump = target.Insert(srclump.Name, target.Lumps.Count, srclump.Length);
srclump.CopyTo(tgtlump);
}
}
}
// This copies specific map lumps from one WAD to another
private void CopyLumpsByType(WAD source, string sourcemapname,
WAD target, string targetmapname,
bool copyrequired, bool copyblindcopy,
bool copynodebuild, bool copyscript)
{
// Find the map header in target
int tgtheaderindex = target.FindLumpIndex(targetmapname);
if (tgtheaderindex == -1)
{
// If this header doesnt exists in the target
// then insert at the end of the target
tgtheaderindex = target.Lumps.Count;
}
// Begin inserting at target header index
int targetindex = tgtheaderindex;
// Find the map header in source
int srcheaderindex = source.FindLumpIndex(sourcemapname);
if (srcheaderindex > -1)
{
// Go for all the map lump names
foreach (KeyValuePair<string, MapLumpInfo> group in config.MapLumps)
{
// Check if this lump should be copied
if((group.Value.Required && copyrequired) || (group.Value.BlindCopy && copyblindcopy) ||
(group.Value.NodeBuild && copynodebuild) || ((group.Value.Script != null || group.Value.ScriptBuild) && copyscript))
{
// Get the lump name
string srclumpname = (group.Key.Contains(CONFIG_MAP_HEADER) ? group.Key.Replace(CONFIG_MAP_HEADER, sourcemapname) : group.Key); //mxd
string tgtlumpname = (group.Key.Contains(CONFIG_MAP_HEADER) ? group.Key.Replace(CONFIG_MAP_HEADER, targetmapname) : group.Key); //mxd
// Find the lump in the source
int sourceindex = FindSpecificLump(source, srclumpname, srcheaderindex, sourcemapname, config.MapLumps);
if (sourceindex > -1)
{
// Remove lump at target
int lumpindex = RemoveSpecificLump(target, tgtlumpname, tgtheaderindex, targetmapname, config.MapLumps);
// Determine target index
// When original lump was found and removed then insert at that position
// otherwise insert after last insertion position
if (lumpindex > -1) targetindex = lumpindex; else targetindex++;
if (targetindex > target.Lumps.Count) targetindex = target.Lumps.Count;
// Copy the lump to the target
//General.WriteLogLine(srclumpname + " copying as " + tgtlumpname);
Lump lump = source.Lumps[sourceindex];
Lump newlump = target.Insert(tgtlumpname, targetindex, lump.Length);
lump.CopyTo(newlump);
}
else
{
// We don't want to bother the user with this. There are a lot of lumps in
// the game configs that are trivial and don't need to be found.
if (group.Value.Required)
{
General.ErrorLogger.Add(ErrorType.Warning, group.Key + " (required lump) should be read but was not found in the WAD file.");
}
}
}
}
}
}
// This finds a lump within the range of known lump names
// Returns -1 when the lump cannot be found
private static int FindSpecificLump(WAD source, string lumpname, int mapheaderindex, string mapheadername, Dictionary<string, MapLumpInfo> maplumps)
{
// Use the configured map lump names to find the specific lump within range,
// because when an unknown lump is met, this search must stop.
// Go for all lumps in order to find the specified lump
for (int i = 0; i < maplumps.Count + 1; i++)
{
// Still within bounds?
if ((mapheaderindex + i) < source.Lumps.Count)
{
// Check if this is a known lump name
string srclumpname = source.Lumps[mapheaderindex + i].Name; //mxd
if (srclumpname.Contains(mapheadername)) srclumpname = srclumpname.Replace(mapheadername, CONFIG_MAP_HEADER);
if (maplumps.ContainsKey(srclumpname)) //mxd
{
// Is this the lump we are looking for?
if (source.Lumps[mapheaderindex + i].Name == lumpname)
{
// Return this index
return mapheaderindex + i;
}
}
else
{
// Unknown lump hit, abort search
break;
}
}
}
// Nothing found
return -1;
}
// This removes a specific lump and returns the position where the lump was removed
// Returns -1 when the lump could not be found
internal static int RemoveSpecificLump(WAD source, string lumpname, int mapheaderindex, string mapheadername, Dictionary<string, MapLumpInfo> maplumps)
{
// Find the specific lump index
int lumpindex = FindSpecificLump(source, lumpname, mapheaderindex, mapheadername, maplumps);
if (lumpindex > -1)
{
// Remove this lump
//General.WriteLogLine(lumpname + " removed");
source.RemoveAt(lumpindex);
}
/*else
{
// Lump not found
General.ErrorLogger.Add(ErrorType.Warning, lumpname + " should be removed but was not found!");
}*/
// Return result
return lumpindex;
}
#endregion
#region ================== Selection Groups
// This adds selection to a group
private void AddSelectionToGroup(int groupindex)
{
General.Interface.SetCursor(Cursors.WaitCursor);
// Make selection
map.AddSelectionToGroup(groupindex); //mxd. switched groupmask to groupindex
General.Interface.DisplayStatus(StatusType.Action, "Assigned selection to group " + (groupindex + 1));
General.Interface.SetCursor(Cursors.Default);
}
// This selects a group
private void SelectGroup(int groupindex)
{
// Select
int groupmask = 0x01 << groupindex;
map.SelectVerticesByGroup(groupmask);
map.SelectLinedefsByGroup(groupmask);
map.SelectSectorsByGroup(groupmask);
map.SelectThingsByGroup(groupmask);
// Redraw to show selection
General.Interface.DisplayStatus(StatusType.Action, "Selected group " + (groupindex + 1));
General.Interface.RedrawDisplay();
}
//mxd. This clears a group
private void ClearGroup(int groupindex)
{
General.Interface.SetCursor(Cursors.WaitCursor);
// Clear group
map.ClearGroup(0x01 << groupindex);
General.Interface.DisplayStatus(StatusType.Action, "Cleared group " + (groupindex + 1));
General.Interface.SetCursor(Cursors.Default);
}
// Select actions
[BeginAction("selectgroup1")]
internal void SelectGroup1() { SelectGroup(0); }
[BeginAction("selectgroup2")]
internal void SelectGroup2() { SelectGroup(1); }
[BeginAction("selectgroup3")]
internal void SelectGroup3() { SelectGroup(2); }
[BeginAction("selectgroup4")]
internal void SelectGroup4() { SelectGroup(3); }
[BeginAction("selectgroup5")]
internal void SelectGroup5() { SelectGroup(4); }
[BeginAction("selectgroup6")]
internal void SelectGroup6() { SelectGroup(5); }
[BeginAction("selectgroup7")]
internal void SelectGroup7() { SelectGroup(6); }
[BeginAction("selectgroup8")]
internal void SelectGroup8() { SelectGroup(7); }
[BeginAction("selectgroup9")]
internal void SelectGroup9() { SelectGroup(8); }
[BeginAction("selectgroup10")]
internal void SelectGroup10() { SelectGroup(9); }
// Assign actions
[BeginAction("assigngroup1")]
internal void AssignGroup1() { AddSelectionToGroup(0); }
[BeginAction("assigngroup2")]
internal void AssignGroup2() { AddSelectionToGroup(1); }
[BeginAction("assigngroup3")]
internal void AssignGroup3() { AddSelectionToGroup(2); }
[BeginAction("assigngroup4")]
internal void AssignGroup4() { AddSelectionToGroup(3); }
[BeginAction("assigngroup5")]
internal void AssignGroup5() { AddSelectionToGroup(4); }
[BeginAction("assigngroup6")]
internal void AssignGroup6() { AddSelectionToGroup(5); }
[BeginAction("assigngroup7")]
internal void AssignGroup7() { AddSelectionToGroup(6); }
[BeginAction("assigngroup8")]
internal void AssignGroup8() { AddSelectionToGroup(7); }
[BeginAction("assigngroup9")]
internal void AssignGroup9() { AddSelectionToGroup(8); }
[BeginAction("assigngroup10")]
internal void AssignGroup10() { AddSelectionToGroup(9); }
//mxd. Clear actions
[BeginAction("cleargroup1")]
internal void ClearGroup1() { ClearGroup(0); }
[BeginAction("cleargroup2")]
internal void ClearGroup2() { ClearGroup(1); }
[BeginAction("cleargroup3")]
internal void ClearGroup3() { ClearGroup(2); }
[BeginAction("cleargroup4")]
internal void ClearGroup4() { ClearGroup(3); }
[BeginAction("cleargroup5")]
internal void ClearGroup5() { ClearGroup(4); }
[BeginAction("cleargroup6")]
internal void ClearGroup6() { ClearGroup(5); }
[BeginAction("cleargroup7")]
internal void ClearGroup7() { ClearGroup(6); }
[BeginAction("cleargroup8")]
internal void ClearGroup8() { ClearGroup(7); }
[BeginAction("cleargroup9")]
internal void ClearGroup9() { ClearGroup(8); }
[BeginAction("cleargroup10")]
internal void ClearGroup10() { ClearGroup(9); }
#endregion
#region ================== Script Editing
// Show the script editor
[BeginAction("openscripteditor")]
internal void ShowScriptEditor()
{
Cursor.Current = Cursors.WaitCursor;
if (scriptwindow == null)
{
// Load the window
scriptwindow = new ScriptEditorForm();
}
// Window not yet visible?
if (!scriptwindow.Visible)
{
// Show the window
if (General.Settings.ScriptOnTop)
{
if (scriptwindow.Visible && (scriptwindow.Owner == null)) scriptwindow.Hide();
scriptwindow.Show(General.MainWindow);
}
else
{
if (scriptwindow.Visible && (scriptwindow.Owner != null)) scriptwindow.Hide();
scriptwindow.Show();
}
}
if(scriptwindow.WindowState == FormWindowState.Minimized) scriptwindow.WindowState = FormWindowState.Normal; //mxd
scriptwindow.Activate();
scriptwindow.Focus();
Cursor.Current = Cursors.Default;
}
// This asks the user to save changes in script files
// Returns false when cancelled by the user
internal bool AskSaveScriptChanges()
{
// Window open?
if (scriptwindow != null)
{
// Ask to save changes
// This also saves implicitly
return scriptwindow.AskSaveAll();
}
// No problems
return true;
}
// This applies the changed status for internal scripts
internal void ApplyScriptChanged()
{
// Remember if lumps are changed
scriptschanged |= scriptwindow.Editor.CheckImplicitChanges();
}
// Close the script editor
// Specify true for the closing parameter when
// the window is already in the closing process
internal void CloseScriptEditor(bool closing)
{
if (scriptwindow != null)
{
if (!scriptwindow.IsDisposed)
{
// Remember what files were open
scriptwindow.Editor.WriteOpenFilesToConfiguration();
// Close now
if (!closing) scriptwindow.Close();
}
// Done
scriptwindow = null;
}
}
// This checks if the scripts are changed
internal bool CheckScriptChanged()
{
if (scriptwindow != null)
{
// Check if scripts are changed
return scriptschanged || scriptwindow.Editor.CheckImplicitChanges();
}
return scriptschanged;
}
// This compiles all lumps that require compiling and stores the results
// Returns true when our code worked properly (even when the compiler returned errors)
private bool CompileScriptLumps()
{
bool success = true;
errors.Clear();
// Go for all the map lumps
foreach (MapLumpInfo lumpinfo in config.MapLumps.Values)
{
// Is this a script lump?
if(lumpinfo.Script != null || lumpinfo.ScriptBuild)
{
// Compile it now
success &= CompileLump(lumpinfo.Name, false);
}
}
return success;
}
// This compiles a script lump and returns any errors that may have occurred
// Returns true when our code worked properly (even when the compiler returned errors)
internal bool CompileLump(string lumpname, bool clearerrors)
{
//mxd. Boilerplate
if(!config.MapLumps.ContainsKey(lumpname))
{
General.ShowErrorMessage("Unable to compile lump '" + lumpname + "'. This lump is not defined in the current game configuration.", MessageBoxButtons.OK);
return false;
}
string inputfile;
Compiler compiler;
string reallumpname = lumpname;
//mxd. Does lump require compiling?
ScriptConfiguration scriptconfig;
if(config.MapLumps[lumpname].ScriptBuild)
{
//mxd. More boilderplate
if(!General.CompiledScriptConfigs.ContainsKey(General.Map.Options.ScriptCompiler))
{
General.ShowErrorMessage("Unable to compile lump '" + lumpname + "'. Unable to find required script compiler configuration ('" + General.Map.Options.ScriptCompiler + "').", MessageBoxButtons.OK);
return false;
}
scriptconfig = General.CompiledScriptConfigs[General.Map.Options.ScriptCompiler];
}
else
{
scriptconfig = config.MapLumps[lumpname].Script;
}
if (scriptconfig.Compiler == null) return true;
// Find the lump
if (lumpname == CONFIG_MAP_HEADER) reallumpname = TEMP_MAP_HEADER;
Lump lump = tempwad.FindLump(reallumpname);
if (lump == null) throw new Exception("No such lump in temporary wad file '" + reallumpname + "'.");
// Determine source file
string sourcefile = (filepathname.Length > 0 ? filepathname : tempwad.Filename);
// New list of errors
if (clearerrors) errors.Clear();
// Determine the script configuration to use
try
{
// Initialize compiler
compiler = scriptconfig.Compiler.Create();
}
catch(Exception e)
{
// Fail
errors.Add(new CompilerError("Unable to initialize compiler. " + e.GetType().Name + ": " + e.Message));
return false;
}
try
{
// Write lump data to temp script file in compiler's temp directory
inputfile = General.MakeTempFilename(compiler.Location, "tmp");
lump.Stream.Seek(0, SeekOrigin.Begin);
BinaryReader reader = new BinaryReader(lump.Stream);
File.WriteAllBytes(inputfile, reader.ReadBytes((int)lump.Stream.Length));
}
catch(Exception e)
{
// Fail
compiler.Dispose();
errors.Add(new CompilerError("Unable to write script to working file. " + e.GetType().Name + ": " + e.Message));
return false;
}
// Make random output filename
string outputfile = General.MakeTempFilename(compiler.Location, "tmp");
// Run compiler
compiler.Parameters = scriptconfig.Parameters;
compiler.InputFile = Path.GetFileName(inputfile);
compiler.OutputFile = Path.GetFileName(outputfile);
compiler.SourceFile = sourcefile;
compiler.WorkingDirectory = Path.GetDirectoryName(inputfile);
if(compiler.Run())
{
// Process errors
foreach(CompilerError e in compiler.Errors)
{
CompilerError newerror = e;
// If the error's filename equals our temporary file,
// use the lump name instead and prefix it with ?
if(string.Compare(e.filename, inputfile, true) == 0)
newerror.filename = "?" + reallumpname;
errors.Add(newerror);
}
// No errors?
if(compiler.Errors.Length == 0)
{
// Output file exists?
if(File.Exists(outputfile))
{
// Copy output file data into a lump?
if(!string.IsNullOrEmpty(scriptconfig.ResultLump))
{
// Do that now then
byte[] filedata;
try
{
filedata = File.ReadAllBytes(outputfile);
}
catch(Exception e)
{
// Fail
compiler.Dispose();
errors.Add(new CompilerError("Unable to read compiler output file. " + e.GetType().Name + ": " + e.Message));
return false;
}
// Store data
MemoryStream stream = new MemoryStream(filedata);
SetLumpData(scriptconfig.ResultLump, stream);
}
}
}
// Clean up
compiler.Dispose();
// Done
return true;
}
// Fail
compiler.Dispose();
errors.Clear(); //mxd
return false;
}
// This clears all compiler errors
/*internal void ClearCompilerErrors()
{
errors.Clear();
}*/
//mxd
internal void UpdateScriptNames()
{
List<ScriptItem> namedscriptslist = new List<ScriptItem>();
List<ScriptItem> numberedscriptslist = new List<ScriptItem>();
// Load the script lumps
foreach (MapLumpInfo maplumpinfo in config.MapLumps.Values)
{
// Is this a script lump?
if ((maplumpinfo.ScriptBuild || maplumpinfo.Script != null) && maplumpinfo.Name == "SCRIPTS")
{
// Load the lump data
MemoryStream stream = GetLumpData(maplumpinfo.Name);
if (stream != null)
{
// Get script names
AcsParserSE parser = new AcsParserSE();
parser.OnInclude = UpdateScriptsFromLocation;
parser.Parse(stream, "SCRIPTS", true, false);
// Add them to arrays
namedscriptslist.AddRange(parser.NamedScripts);
numberedscriptslist.AddRange(parser.NumberedScripts);
scriptincludes.AddRange(parser.Includes);
}
}
}
// Sort script names
namedscriptslist.Sort(ScriptItem.SortByName);
numberedscriptslist.Sort(ScriptItem.SortByIndex);
// Add to collections
namedscripts = new Dictionary<string, ScriptItem>(namedscriptslist.Count);
numberedscripts = new Dictionary<int, ScriptItem>(numberedscriptslist.Count);
foreach (ScriptItem item in namedscriptslist)
{
if(!namedscripts.ContainsKey(item.Name.ToLowerInvariant()))
namedscripts.Add(item.Name.ToLowerInvariant(), item);
}
foreach(ScriptItem item in numberedscriptslist)
{
if(!numberedscripts.ContainsKey(item.Index))
numberedscripts.Add(item.Index, item);
}
}
//mxd
private static void UpdateScriptsFromLocation(AcsParserSE parser, string path)
{
MemoryStream s = General.Map.Data.LoadFile(path);
if(s != null && s.Length > 0) parser.Parse(s, path, true, true);
}
#endregion
#region ================== Methods
// This updates everything after the configuration or settings have been changed
internal void UpdateConfiguration()
{
// Update map
map.UpdateConfiguration();
// Update settings
renderer3d.CreateProjection();
renderer3d.UpdateVertexHandle(); //mxd
// Things filters
General.MainWindow.UpdateThingsFilters();
}
// This changes thing filter
public void ChangeThingFilter(ThingsFilter newfilter)
{
// We have a special filter for null
if (newfilter == null) newfilter = new NullThingsFilter();
// Deactivate old filter
if (thingsfilter != null) thingsfilter.Deactivate();
// Change
thingsfilter = newfilter;
// Activate filter
thingsfilter.Activate();
// Update interface
General.MainWindow.ReflectThingsFilter();
// Redraw
General.MainWindow.RedrawDisplay();
}
// This sets a new mapset for editing
internal void ChangeMapSet(MapSet newmap)
{
// Let the plugin and editing mode know
General.Plugins.OnMapSetChangeBegin();
if (General.Editing.Mode != null) General.Editing.Mode.OnMapSetChangeBegin();
this.visualcamera.Sector = null;
// Can't have a selection in an old map set
map.ClearAllSelected();
// Reset surfaces
renderer2d.Surfaces.Reset();
// Apply
map.Dispose();
map = newmap;
map.UpdateConfiguration();
map.SnapAllToAccuracy();
map.Update();
thingsfilter.Update();
// Let the plugin and editing mode know
General.Plugins.OnMapSetChangeEnd();
if (General.Editing.Mode != null) General.Editing.Mode.OnMapSetChangeEnd();
}
// This reloads resources
[BeginAction("reloadresources")]
internal void DoReloadResource()
{
//mxd. Get rid of old errors
General.ErrorLogger.Clear();
// Set this to false so we can see if errors are added
General.ErrorLogger.IsErrorAdded = false;
#if DEBUG
DebugConsole.Clear();
#endif
ReloadResources();
if (General.ErrorLogger.IsErrorAdded)
{
// Show any errors if preferred
General.MainWindow.DisplayStatus(StatusType.Warning, "There were errors during resources loading!");
if (General.Settings.ShowErrorsWindow) General.MainWindow.ShowErrors();
}
else
{
General.MainWindow.DisplayReady();
}
}
internal void ReloadResources()
{
// Keep old display info
StatusInfo oldstatus = General.MainWindow.Status;
Cursor oldcursor = Cursor.Current;
// Show status
General.MainWindow.DisplayStatus(StatusType.Busy, "Reloading data resources...");
Cursor.Current = Cursors.WaitCursor;
// Clean up
data.Dispose();
data = null;
config = null;
configinfo = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect(); //mxd
// Clear errors
General.ErrorLogger.Clear();
// Reload game configuration
General.WriteLogLine("Reloading game configuration...");
configinfo = General.GetConfigurationInfo(options.ConfigFile);
config = new GameConfiguration(configinfo.Configuration); //mxd
General.Editing.UpdateCurrentEditModes();
// Reload data resources
General.WriteLogLine("Reloading data resources...");
data = new DataManager();
if (!string.IsNullOrEmpty(filepathname))
{
DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, false, false, false);
data.Load(configinfo.Resources, options.Resources, maplocation);
}
else
{
data.Load(configinfo.Resources, options.Resources);
}
// Apply new settings to map elements
map.UpdateConfiguration();
// Re-link the background image
grid.LinkBackground();
// Inform all plugins that the resources are reloaded
General.Plugins.ReloadResources();
// Inform editing mode that the resources are reloaded
if (General.Editing.Mode != null) General.Editing.Mode.OnReloadResources();
// Reset status
General.MainWindow.DisplayStatus(oldstatus);
Cursor.Current = oldcursor;
//mxd
UpdateScriptNames();
}
// Game Configuration action
[BeginAction("mapoptions")]
internal void ShowMapOptions()
{
// Cancel volatile mode, if any
General.Editing.DisengageVolatileMode();
// Show map options dialog
MapOptionsForm optionsform = new MapOptionsForm(options, false);
if (optionsform.ShowDialog(General.MainWindow) == DialogResult.OK)
{
// Update interface
//General.MainWindow.UpdateInterface();
// Stop data manager
data.Dispose();
// Apply new options
this.options = optionsform.Options;
// Load new game configuration
General.WriteLogLine("Loading game configuration '" + options.ConfigFile + "'...");
configinfo = General.GetConfigurationInfo(options.ConfigFile);
Type oldiotype = io.GetType(); //mxd
config = new GameConfiguration(configinfo.Configuration); //mxd
configinfo.ApplyDefaults(config);
General.Editing.UpdateCurrentEditModes();
// Setup new map format IO
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
//mxd. Some lumps may've become unneeded during map format conversion.
if(oldiotype != io.GetType())
RemoveUnneededLumps(tempwad, TEMP_MAP_HEADER, false);
// Create required lumps if they don't exist yet
CreateRequiredLumps(tempwad, TEMP_MAP_HEADER);
// Let the plugins know
General.Plugins.MapReconfigure();
//mxd. Update linedef color presets and flags if required
if(oldiotype == typeof(UniversalMapSetIO) && !(io is UniversalMapSetIO))
{
foreach(Linedef l in General.Map.Map.Linedefs) l.TranslateFromUDMF();
foreach(Thing t in General.Map.Map.Things) t.TranslateFromUDMF();
}
else if(oldiotype != typeof(UniversalMapSetIO) && io is UniversalMapSetIO)
{
foreach(Linedef l in General.Map.Map.Linedefs) l.TranslateToUDMF(oldiotype);
foreach(Thing t in General.Map.Map.Things) t.TranslateToUDMF();
}
else if(oldiotype != typeof(DoomMapSetIO) && io is DoomMapSetIO)
{
//drop all arguments
foreach (Linedef l in General.Map.Map.Linedefs)
for(int i = 0; i < l.Args.Length; i++) l.Args[i] = 0;
foreach (Thing t in General.Map.Map.Things)
for(int i = 0; i < t.Args.Length; i++) t.Args[i] = 0;
}
map.UpdateCustomLinedefColors();
// Update interface
General.MainWindow.SetupInterface();
General.MainWindow.UpdateThingsFilters();
General.MainWindow.UpdateLinedefColorPresets(); //mxd
General.MainWindow.UpdateInterface();
// Reload resources
ReloadResources();
UpdateScriptNames(); //mxd
//mxd. Translate texture names
bool nameschanged = map.TranslateTextureNames(config.UseLongTextureNames, false);
grid.TranslateBackgroundName(config.UseLongTextureNames);
//mxd. Sector textures may've been changed
if(nameschanged) data.UpdateUsedTextures();
// Done
General.MainWindow.DisplayReady();
General.MainWindow.RedrawDisplay(); //mxd
}
// Done
optionsform.Dispose();
}
// This shows the things filters setup
[BeginAction("thingsfilterssetup")]
internal void ShowThingsFiltersSetup()
{
new ThingsFiltersForm().ShowDialog(General.MainWindow);
}
//mxd. This shows the linedef color presets window
[BeginAction("linedefcolorssetup")]
internal void ShowLinedefColorsSetup()
{
// Show things filter dialog
new LinedefColorPresetsForm().ShowDialog(General.MainWindow);
}
// This returns true is the given type matches
public bool IsType(Type t)
{
return io.GetType() == t;
}
//mxd
public SizeF GetTextSize(string text, float scale)
{
return graphics.Font.GetTextSize(text, scale);
}
//mxd
[BeginAction("snapvertstogrid")]
private void SnapSelectedMapElementsToGrid()
{
// Get selected elements
ICollection<Vertex> verts = map.GetSelectedVertices(true);
ICollection<Linedef> lines = map.GetSelectedLinedefs(true); // Sector lines are auto-selected when a sector is selected
ICollection<Thing> things = map.GetSelectedThings(true);
// Get vertices from selection
Dictionary<int, Vertex> vertstosnap = new Dictionary<int, Vertex>(verts.Count);
foreach(Vertex v in verts) vertstosnap.Add(v.Index, v);
foreach(Linedef l in lines)
{
if(!vertstosnap.ContainsKey(l.Start.Index)) vertstosnap.Add(l.Start.Index, l.Start);
if(!vertstosnap.ContainsKey(l.End.Index)) vertstosnap.Add(l.End.Index, l.End);
}
// Anything to snap?
if (vertstosnap.Count == 0 && things.Count == 0)
{
General.Interface.DisplayStatus(StatusType.Warning, "Select any map element first!");
return;
}
// Make undo
undoredo.CreateUndo("Snap map elements to grid");
// Do the snapping
Cursor.Current = Cursors.AppStarting;
// Snap vertices?
int snappedverts = (vertstosnap.Count > 0 ? SnapVertices(vertstosnap.Values) : 0);
// Snap things?..
int snappedthings = (things.Count > 0 ? SnapThings(things) : 0);
// Assemble status message
List<string> message = new List<string>();
if(snappedverts > 0) message.Add(snappedverts + " vertices");
if(snappedthings > 0) message.Add(snappedthings + " things");
// Map changed?
if (message.Count > 0)
{
// Display status
General.Interface.DisplayStatus(StatusType.Info, "Snapped " + string.Join(" and ", message.ToArray()));
// Warn the user
if(snappedverts > 0)
{
MessageBox.Show("Snapped " + snappedverts + " vertices to grid." + Environment.NewLine +
"It's a good idea to run Map Analysis Mode now.");
}
// Invoke clear selection to update sector highlight overlay
General.Actions.InvokeAction("builder_clearselection");
// Update cached values
General.Map.Map.Update();
// Map is changed
General.Map.IsChanged = true;
}
else
{
// Display status
General.Interface.DisplayStatus(StatusType.Info, "Selected map elements were already on the grid.");
// Withdraw undo
undoredo.WithdrawUndo();
}
// Done
General.Interface.RedrawDisplay();
Cursor.Current = Cursors.Default;
}
//mxd
private int SnapVertices(IEnumerable<Vertex> verts)
{
int snappedCount = 0;
List<Vertex> movedVerts = new List<Vertex>();
List<Linedef> movedLines = new List<Linedef>();
//snap them all!
foreach(Vertex v in verts)
{
Vector2D pos = v.Position;
v.SnapToGrid();
if(v.Position.x != pos.x || v.Position.y != pos.y)
{
snappedCount++;
movedVerts.Add(v);
foreach(Linedef l in v.Linedefs)
{
if(!movedLines.Contains(l)) movedLines.Add(l);
}
}
}
//Create blockmap
RectangleF area = MapSet.CreateArea(General.Map.Map.Vertices);
BlockMap<BlockEntry> blockmap = new BlockMap<BlockEntry>(area);
blockmap.AddVerticesSet(General.Map.Map.Vertices);
//merge overlapping vertices using teh power of BLOCKMAP!!!11
BlockEntry block;
foreach(Vertex v in movedVerts)
{
block = blockmap.GetBlockAt(v.Position);
if(block == null) continue;
foreach(Vertex blockVert in block.Vertices)
{
if(blockVert.IsDisposed || blockVert.Index == v.Index || blockVert.Position != v.Position)
continue;
foreach (Linedef l in blockVert.Linedefs)
{
if (!movedLines.Contains(l)) movedLines.Add(l);
}
v.Join(blockVert);
break;
}
}
// Update cached values of lines because we may need their length/angle
General.Map.Map.Update(true, false);
General.Map.Map.BeginAddRemove();
MapSet.RemoveLoopedLinedefs(movedLines);
MapSet.JoinOverlappingLines(movedLines);
General.Map.Map.EndAddRemove();
//get changed sectors
List<Sector> changedSectors = new List<Sector>();
foreach(Linedef l in movedLines)
{
if(l == null || l.IsDisposed) continue;
if(l.Front != null && l.Front.Sector != null && !changedSectors.Contains(l.Front.Sector))
changedSectors.Add(l.Front.Sector);
if(l.Back != null && l.Back.Sector != null && !changedSectors.Contains(l.Back.Sector))
changedSectors.Add(l.Back.Sector);
}
// Now update area of sectors
General.Map.Map.Update(false, true);
//fix invalid sectors
foreach(Sector s in changedSectors)
{
if(s.BBox.IsEmpty)
{
s.Dispose();
}
else if(s.Sidedefs.Count < 3)
{
bool merged = false;
foreach(Sidedef side in s.Sidedefs)
{
if(side.Other != null && side.Other.Sector != null)
{
s.Join(side.Other.Sector);
merged = true;
break;
}
}
//oh well, I don't know what else I can do here...
if(!merged) s.Dispose();
}
}
return snappedCount;
}
//mxd
private static int SnapThings(IEnumerable<Thing> things)
{
int snappedCount = 0;
//snap them all!
foreach(Thing t in things)
{
Vector2D pos = t.Position;
t.SnapToGrid();
if(t.Position.x != pos.x || t.Position.y != pos.y) snappedCount++;
}
return snappedCount;
}
#endregion
}
}