#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Windows.Forms; using System.IO; using System.Reflection; using System.Diagnostics; using CodeImp.DoomBuilder.Windows; using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Editing; using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Actions; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Plugins; using CodeImp.DoomBuilder.Compilers; using CodeImp.DoomBuilder.VisualModes; #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; // 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 errors; private VisualCamera visualcamera; // Disposing private bool isdisposed = false; #endregion #region ================== Properties public string FilePathName { get { return filepathname; } } public string FileTitle { get { return filetitle; } } 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; } } internal 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 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; } } #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(); } // 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(); // 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; } else { // Already closed return true; } } #endregion #region ================== New / Open // Initializes for a new map internal bool InitializeNewMap(MapOptions options) { string tempfile; // Apply settings this.filetitle = "unnamed.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(General.LoadGameConfiguration(options.ConfigFile)); 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 // 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(); // 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; DataLocation maplocation; // 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(General.LoadGameConfiguration(options.ConfigFile)); 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(); maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false); data.Load(configinfo.Resources, options.Resources, maplocation); // Remove unused sectors map.RemoveUnusedSectors(true); // Update structures options.ApplyGridSettings(); map.UpdateConfiguration(); map.SnapAllToAccuracy(); map.Update(); thingsfilter.Update(); // 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; } #endregion #region ================== Save // Initializes for an existing map internal bool SaveMap(string newfilepathname, SavePurpose purpose) { MapSet outputset; string nodebuildername, settingsfile; StatusInfo oldstatus; WAD targetwad; int index; bool includenodes = false; string origmapname; bool success = true; 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) { if(!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 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 flagkeys = new List(t.Flags.Keys); foreach(string k in flagkeys) t.Flags[k] = false; } } // Do we need sidedefs compression? 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; } } // 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 nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave; // Build the nodes oldstatus = General.MainWindow.Status; General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes..."); if(!string.IsNullOrEmpty(nodebuildername)) includenodes = BuildNodes(nodebuildername, true); else includenodes = false; 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 origmapname = (options.PreviousName != "") ? options.PreviousName : options.CurrentName; try { // Backup existing file, if any if(File.Exists(newfilepathname)) { 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 string 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 CopyAllLumpsExceptMap(origwad, targetwad, 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); 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); 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); // Was the map lump name renamed? if((options.PreviousName != options.CurrentName) && (options.PreviousName != "")) { General.WriteLogLine("Renaming map lump 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); options.PreviousName = ""; } 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 success; } #endregion #region ================== Nodebuild // This builds the nodes in the temproary file with the given configuration name private bool BuildNodes(string nodebuildername, bool failaswarning) { NodebuilderInfo nodebuilder; string tempfile1, tempfile2, sourcefile; bool lumpscomplete = false; WAD buildwad; // Find the nodebuilder 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 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 if(filepathname.Length > 0) sourcefile = filepathname; else sourcefile = tempwad.Filename; // 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 { // 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.", MessageBoxButtons.OK); else General.ShowErrorMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures.", MessageBoxButtons.OK); } // Done with the build wad if(buildwad != null) buildwad.Dispose(); } // 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(DictionaryEntry ml in config.MapLumpNames) { // Read lump settings from map config bool lumpnodebuild = config.ReadSetting("maplumpnames." + ml.Key + ".nodebuild", false); bool lumpallowempty = config.ReadSetting("maplumpnames." + ml.Key + ".allowempty", false); // Check if this lump should exist if(lumpnodebuild && !lumpallowempty) { // Find the lump in the source if(wad.FindLump(ml.Key.ToString(), srcindex, srcindex + config.MapLumpNames.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 internal MemoryStream GetLumpData(string lumpname) { Lump l = tempwad.FindLump(lumpname); if(l != null) { l.Stream.Seek(0, SeekOrigin.Begin); return new MemoryStream(l.Stream.ReadAllBytes()); } else { return null; } } // This writes a copy of the data to a lump in the temp file internal 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 creates empty lumps for those required private void CreateRequiredLumps(WAD target, string mapname) { int headerindex, insertindex, targetindex; string lumpname; bool lumprequired; // Find the map header in target 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 insertindex = headerindex; // Go for all the map lump names foreach(DictionaryEntry ml in config.MapLumpNames) { // Read lump settings from map config lumprequired = config.ReadSetting("maplumpnames." + ml.Key + ".required", false); // Check if this lump is required if(lumprequired) { // Get the lump name lumpname = ml.Key.ToString(); if(lumpname == CONFIG_MAP_HEADER) lumpname = mapname; // Check if the lump is missing at the target targetindex = FindSpecificLump(target, lumpname, headerindex, mapname, config.MapLumpNames); 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; } } } } // This copies all lumps, except those of a specific map private void CopyAllLumpsExceptMap(WAD source, WAD target, 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 && !config.MapLumpNames.Contains(srclump.Name)) { // 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) { bool lumprequired, lumpblindcopy, lumpnodebuild; string lumpscript, srclumpname, tgtlumpname; int srcheaderindex, tgtheaderindex, targetindex, sourceindex, lumpindex; Lump lump, newlump; // Find the map header in target 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 targetindex = tgtheaderindex; // Find the map header in source srcheaderindex = source.FindLumpIndex(sourcemapname); if(srcheaderindex > -1) { // Copy the map header from source to target //newlump = target.Insert(targetmapname, tgtindex++, source.Lumps[srcindex].Length); //source.Lumps[srcindex].CopyTo(newlump); // Go for all the map lump names foreach(DictionaryEntry ml in config.MapLumpNames) { // Read lump settings from map config lumprequired = config.ReadSetting("maplumpnames." + ml.Key + ".required", false); lumpblindcopy = config.ReadSetting("maplumpnames." + ml.Key + ".blindcopy", false); lumpnodebuild = config.ReadSetting("maplumpnames." + ml.Key + ".nodebuild", false); lumpscript = config.ReadSetting("maplumpnames." + ml.Key + ".script", ""); // Check if this lump should be copied if((lumprequired && copyrequired) || (lumpblindcopy && copyblindcopy) || (lumpnodebuild && copynodebuild) || ((lumpscript.Length != 0) && copyscript)) { // Get the lump name srclumpname = ml.Key.ToString(); tgtlumpname = ml.Key.ToString(); if(srclumpname == CONFIG_MAP_HEADER) srclumpname = sourcemapname; if(tgtlumpname == CONFIG_MAP_HEADER) tgtlumpname = targetmapname; // Find the lump in the source sourceindex = FindSpecificLump(source, srclumpname, srcheaderindex, sourcemapname, config.MapLumpNames); if(sourceindex > -1) { // Remove lump at target lumpindex = RemoveSpecificLump(target, tgtlumpname, tgtheaderindex, targetmapname, config.MapLumpNames); // 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 = source.Lumps[sourceindex]; 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(lumprequired) { General.ErrorLogger.Add(ErrorType.Warning, ml.Key.ToString() + " (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 internal static int FindSpecificLump(WAD source, string lumpname, int mapheaderindex, string mapheadername, IDictionary 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 if(maplumps.Contains(source.Lumps[mapheaderindex + i].Name) || (maplumps.Contains(CONFIG_MAP_HEADER) && (source.Lumps[mapheaderindex + i].Name == mapheadername))) { // 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, IDictionary maplumps) { int lumpindex; // Find the specific lump index 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 undo undoredo.CreateUndo("Assign to group " + groupindex); // Make selection map.AddSelectionToGroup(0x01 << groupindex); General.Interface.DisplayStatus(StatusType.Action, "Assigned selection to group " + groupindex); 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); General.Interface.RedrawDisplay(); } // 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); } #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(); } } 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(); } else { // 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(); } else { // Check if scripts are changed 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) { // 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) { string inputfile, outputfile, sourcefile; Compiler compiler; byte[] filedata; string reallumpname = lumpname; // 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 if(filepathname.Length > 0) sourcefile = filepathname; else sourcefile = tempwad.Filename; // New list of errors if(clearerrors) errors.Clear(); // Determine the script configuration to use ScriptConfiguration scriptconfig = config.MapLumps[lumpname].script; if(scriptconfig.Compiler != null) { 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 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 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; } else { // Fail compiler.Dispose(); errors = null; return false; } } else { // No compiler to run for this script type return true; } } // This clears all compiler errors internal void ClearCompilerErrors() { errors.Clear(); } #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(); // 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() { // Set this to false so we can see if errors are added General.ErrorLogger.IsErrorAdded = false; 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() { DataLocation maplocation; StatusInfo oldstatus; Cursor oldcursor; // Keep old display info oldstatus = General.MainWindow.Status; 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(); // Reload game configuration General.WriteLogLine("Reloading game configuration..."); configinfo = General.GetConfigurationInfo(options.ConfigFile); config = new GameConfiguration(General.LoadGameConfiguration(options.ConfigFile)); General.Editing.UpdateCurrentEditModes(); // Reload data resources General.WriteLogLine("Reloading data resources..."); data = new DataManager(); if(!string.IsNullOrEmpty(filepathname)) { 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; } // 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); 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..."); configinfo = General.GetConfigurationInfo(options.ConfigFile); config = new GameConfiguration(General.LoadGameConfiguration(options.ConfigFile)); 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); // Create required lumps if they don't exist yet CreateRequiredLumps(tempwad, TEMP_MAP_HEADER); // Let the plugins know General.Plugins.MapReconfigure(); // Update interface General.MainWindow.SetupInterface(); General.MainWindow.UpdateThingsFilters(); General.MainWindow.UpdateInterface(); // Reload resources ReloadResources(); // Done General.MainWindow.DisplayReady(); } // Done optionsform.Dispose(); } // This shows the things filters setup [BeginAction("thingsfilterssetup")] internal void ShowThingsFiltersSetup() { // Show things filter dialog ThingsFiltersForm f = new ThingsFiltersForm(); f.ShowDialog(General.MainWindow); f.Dispose(); General.MainWindow.UpdateThingsFilters(); } // This returns true is the given type matches public bool IsType(Type t) { return io.GetType().Equals(t); } #endregion } }