UltimateZoneBuilder/Source/Core/General/MapManager.cs
MaxED 6d0823b943 Fixed, ACS parser: fixed a crash when trying to load zero-length acs lump/file.
Fixed, Visual mode: fixed incorrect GLDEFS glow brightness calculation for Things.
Updated ZDoom_DECORATE.cfg.
2015-11-28 20:59:32 +00:00

2437 lines
No EOL
77 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;
private bool maploading; //mxd
// 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 readonly HashSet<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; if(!maploading) General.MainWindow.UpdateMapChangedStatus(); } }
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. Map format
public bool UDMF { get { return config.UDMF; } }
public bool HEXEN { get { return config.HEXEN; } }
public bool DOOM { get { return config.DOOM; } }
//mxd. Scripts
internal Dictionary<string, ScriptItem> NamedScripts { get { return namedscripts; } }
internal Dictionary<int, ScriptItem> NumberedScripts { get { return numberedscripts; } }
internal HashSet<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 HashSet<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
maploading = true; //mxd
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.maploading = true; //mxd
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;
this.maploading = false; //mxd
General.WriteLogLine("Map creation done");
General.MainWindow.UpdateMapChangedStatus(); //mxd
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.maploading = true; //mxd
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();
//mxd. Create MapSet
bool maprestored;
if(!CreateMapSet(map, filepathname, options, out maprestored)) return false;
// 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. Update includes list and script names
UpdateScriptNames(true);
//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 = maprestored; //mxd
this.maploading = false; //mxd
General.WriteLogLine("Map loading done");
General.MainWindow.UpdateMapChangedStatus(); //mxd
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.maploading = true; //mxd
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();
//mxd. Create MapSet
bool maprestored;
if(!CreateMapSet(newmap, filepathname, options, out maprestored)) return false;
//mxd. And switch to it
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. Update includes list and script names
UpdateScriptNames(true);
//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 = maprestored;
this.maploading = false; //mxd
General.WriteLogLine("Map switching done");
General.MainWindow.UpdateMapChangedStatus(); //mxd
return true;
}
//mxd
private bool CreateMapSet(MapSet newmap, string filepathname, MapOptions options, out bool maprestored)
{
maprestored = false;
string wadname = Path.GetFileNameWithoutExtension(filepathname);
if(!string.IsNullOrEmpty(wadname))
{
string hash = MurmurHash2.Hash(wadname + options.LevelName + File.GetLastWriteTime(filepathname)).ToString();
string backuppath = Path.Combine(General.MapRestorePath, wadname + "." + hash + ".restore");
// Backup exists and it's newer than the map itself?
if(File.Exists(backuppath) && File.GetLastWriteTime(backuppath) > File.GetLastWriteTime(filepathname))
{
if(General.ShowWarningMessage("Looks like your previous editing session has gone terribly wrong." + Environment.NewLine
+ "Would you like to restore the map from the backup?", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
General.WriteLogLine("Restoring map from '" + backuppath + "'...");
#if DEBUG
// Restore map
newmap.Deserialize(SharpCompressHelper.DecompressStream(new MemoryStream(File.ReadAllBytes(backuppath))));
#else
try
{
// Restore map
newmap.Deserialize(SharpCompressHelper.DecompressStream(new MemoryStream(File.ReadAllBytes(backuppath))));
// Delete the backup
File.Delete(backuppath);
}
catch(Exception e)
{
General.ErrorLogger.Add(ErrorType.Error, "Unable to restore the map data structures from the backup. " + e.GetType().Name + ": " + e.Message);
General.ShowErrorMessage("Unable to restore the map data structures from the backup.", MessageBoxButtons.OK);
return false;
}
#endif
maprestored = true;
}
}
}
// Read the map from temp file
if(!maprestored)
{
newmap.BeginAddRemove();
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
General.WriteLogLine("Reading map data 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 with the specified configuration. " + e.GetType().Name + ": " + e.Message);
General.ShowErrorMessage("Unable to read the map data with the specified configuration." + Environment.NewLine + Environment.NewLine + e.Message, MessageBoxButtons.OK);
return false;
}
#endif
newmap.EndAddRemove();
}
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)
{
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();
//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);
data.Resume();
General.WriteLogLine("Map saving failed: failed to close " + Path.GetFileName(process.MainModule.FileName));
return false;
}
}
// Retry
data.Resume();
General.WriteLogLine("Map saving restarted...");
return SaveMap(newfilepathname, purpose);
}
else
{
data.Resume();
General.WriteLogLine("Map saving cancelled...");
return false;
}
}
else
{
General.ShowErrorMessage(checkresult.Error, MessageBoxButtons.OK);
data.Resume();
General.WriteLogLine("Map saving failed: " + checkresult.Error);
return false;
}
}
// 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");
General.MainWindow.UpdateMapChangedStatus(); //mxd
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";
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;
}
//mxd
internal void SaveMapBackup()
{
if(isdisposed || map == null || map.IsDisposed || string.IsNullOrEmpty(filepathname) || options == null)
{
General.WriteLogLine("Map backup saving failed: required structures already disposed...");
return;
}
#if !DEBUG
try
{
#endif
string wadname = Path.GetFileNameWithoutExtension(filepathname);
if(!string.IsNullOrEmpty(wadname))
{
// Make backup file path
if(!Directory.Exists(General.MapRestorePath)) Directory.CreateDirectory(General.MapRestorePath);
string hash = MurmurHash2.Hash(wadname + options.LevelName + File.GetLastWriteTime(filepathname)).ToString();
string backuppath = Path.Combine(General.MapRestorePath, wadname + "." + hash + ".restore");
// Export map
MemoryStream ms = map.Serialize();
ms.Seek(0, SeekOrigin.Begin);
File.WriteAllBytes(backuppath, SharpCompressHelper.CompressStream(ms).ToArray());
// Log it
General.WriteLogLine("Map backup saved to '" + backuppath + "'");
}
else
{
// Log it
General.WriteLogLine("Map backup saving failed: invalid map WAD name");
}
#if !DEBUG
}
catch(Exception e)
{
// Log it
General.WriteLogLine("Map backup saving failed: " + e.Source + ": " + e.Message);
}
#endif
}
#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. Update includes list and script names
internal List<CompilerError> UpdateScriptNames(bool logerrors)
{
List<CompilerError> compilererrors = UpdateScriptNames();
if(logerrors && compilererrors.Count > 0)
{
foreach(CompilerError error in compilererrors)
{
General.ErrorLogger.Add(ErrorType.Error, "ACS error in '" + error.filename
+ "', line " + error.linenumber + ". " + error.description + ".");
}
}
return compilererrors;
}
//mxd. Update includes list and script names
internal List<CompilerError> UpdateScriptNames()
{
List<ScriptItem> namedscriptslist = new List<ScriptItem>();
List<ScriptItem> numberedscriptslist = new List<ScriptItem>();
List<string> scripincludeslist = new List<string>();
List<CompilerError> compilererrors = new List<CompilerError>();
// 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")
{
ScriptConfiguration scriptconfig;
if(maplumpinfo.ScriptBuild)
{
//mxd. More boilderplate
if(!General.CompiledScriptConfigs.ContainsKey(General.Map.Options.ScriptCompiler))
{
compilererrors.Add(new CompilerError("Unable to compile lump '" + maplumpinfo.Name + "'. Unable to find required script compiler configuration ('" + General.Map.Options.ScriptCompiler + "')."));
return compilererrors;
}
scriptconfig = General.CompiledScriptConfigs[General.Map.Options.ScriptCompiler];
}
else
{
scriptconfig = maplumpinfo.Script;
}
// Load the lump data
MemoryStream stream = GetLumpData(maplumpinfo.Name);
if(stream != null && stream.Length > 0 && scriptconfig != null && scriptconfig.Compiler != null)
{
// Get script names
AcsParserSE parser = new AcsParserSE { OnInclude = UpdateScriptsFromLocation };
if(parser.Parse(stream, "SCRIPTS", scriptconfig.Compiler.Files, true, false))
{
// Add them to arrays
namedscriptslist.AddRange(parser.NamedScripts);
numberedscriptslist.AddRange(parser.NumberedScripts);
scripincludeslist.AddRange(parser.Includes);
}
// Check for errors
else if(parser.HasError)
{
compilererrors.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
break;
}
}
}
}
// Add to collections
scriptincludes.Clear();
if(compilererrors.Count == 0)
{
namedscripts = new Dictionary<string, ScriptItem>(namedscriptslist.Count);
numberedscripts = new Dictionary<int, ScriptItem>(numberedscriptslist.Count);
// Sort script names
namedscriptslist.Sort(ScriptItem.SortByName);
numberedscriptslist.Sort(ScriptItem.SortByIndex);
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);
foreach(string include in scripincludeslist)
if(!scriptincludes.Contains(include)) scriptincludes.Add(include);
}
else
{
// Clear collections
namedscripts.Clear();
numberedscripts.Clear();
}
return compilererrors;
}
//mxd
private static void UpdateScriptsFromLocation(AcsParserSE parser, string path)
{
parser.Parse(General.Map.Data.LoadFile(path), 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
private 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. Update includes list and script names
UpdateScriptNames(true);
}
// 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();
//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
}
}