mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
2622 lines
No EOL
85 KiB
C#
Executable file
2622 lines
No EOL
85 KiB
C#
Executable file
|
|
#region ================== Copyright (c) 2007 Pascal vd Heiden
|
|
|
|
/*
|
|
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
|
|
* This program is released under GNU General Public License
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#endregion
|
|
|
|
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.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.Data.Scripting;
|
|
using CodeImp.DoomBuilder.Editing;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using CodeImp.DoomBuilder.GZBuilder.Data; //mxd
|
|
using CodeImp.DoomBuilder.IO;
|
|
using CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.VisualModes;
|
|
using CodeImp.DoomBuilder.Windows;
|
|
using CodeImp.DoomBuilder.ZDoom.Scripting;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder
|
|
{
|
|
public sealed class MapManager : IDisposable
|
|
{
|
|
#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";
|
|
private const int REPLACE_TARGET_MAP = -1; //mxd
|
|
|
|
#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 WADReader tempwadreader;
|
|
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;
|
|
|
|
// 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; } }
|
|
internal WADReader TemporaryMapFile { get { return tempwadreader; } } //mxd
|
|
|
|
//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; } }
|
|
|
|
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>(StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
// Disposer
|
|
public void 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(tempwadreader != null) tempwadreader.Dispose(); //mxd
|
|
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;
|
|
tempwadreader = null; //mxd
|
|
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;
|
|
}
|
|
}
|
|
|
|
#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 = string.Empty;
|
|
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();
|
|
|
|
//mxd. Check if default script compiler is required
|
|
if(string.IsNullOrEmpty(configinfo.DefaultScriptCompiler))
|
|
{
|
|
foreach(MapLumpInfo info in config.MapLumps.Values)
|
|
{
|
|
if(info.ScriptBuild)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "\"DefaultScriptCompiler\" property is not set in \"" + configinfo + "\" game configuration. The editor may fail to compile ACC scripts.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create map data
|
|
map = new MapSet();
|
|
|
|
// Create temp wadfile
|
|
DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false); //mxd
|
|
General.WriteLogLine("Creating temporary file: " + templocation.location);
|
|
#if DEBUG
|
|
tempwadreader = new WADReader(templocation, false, true);
|
|
#else
|
|
try { tempwadreader = new WADReader(templocation, false, true); }
|
|
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, tempwadreader.WadFile, this);
|
|
|
|
// Create required lumps
|
|
General.WriteLogLine("Creating map data structures...");
|
|
tempwadreader.WadFile.Insert(TEMP_MAP_HEADER, 0, 0);
|
|
io.Write(map, TEMP_MAP_HEADER, 1);
|
|
CreateRequiredLumps(tempwadreader.WadFile, 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>(StringComparer.OrdinalIgnoreCase); //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;
|
|
|
|
#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
|
|
DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false); //mxd
|
|
General.WriteLogLine("Creating temporary file: " + templocation.location);
|
|
#if DEBUG
|
|
tempwadreader = new WADReader(templocation, false, true);
|
|
#else
|
|
try { tempwadreader = new WADReader(templocation, false, true); } 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, tempwadreader.WadFile, TEMP_MAP_HEADER, REPLACE_TARGET_MAP, 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. Flip linedefs with only back side
|
|
int flipsdone = MapSet.FlipBackwardLinedefs(map.Linedefs);
|
|
if(flipsdone > 0) General.WriteLogLine(flipsdone + " single-sided linedefs were flipped.");
|
|
|
|
// Update structures
|
|
options.ApplyGridSettings();
|
|
map.UpdateConfiguration();
|
|
map.SnapAllToAccuracy();
|
|
map.Update();
|
|
thingsfilter.Update();
|
|
|
|
//mxd. Update script names
|
|
LoadACS();
|
|
|
|
//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;
|
|
this.options = options;
|
|
|
|
// Create map data
|
|
MapSet newmap = new MapSet();
|
|
WAD mapwad;
|
|
|
|
// Create temp wadfile
|
|
DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false); //mxd
|
|
General.WriteLogLine("Creating temporary file: " + templocation.location);
|
|
if(tempwadreader != null) tempwadreader.Dispose();
|
|
|
|
#if DEBUG
|
|
tempwadreader = new WADReader(templocation, false, true);
|
|
#else
|
|
try { tempwadreader = new WADReader(templocation, false, true); } 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, tempwadreader.WadFile, TEMP_MAP_HEADER, REPLACE_TARGET_MAP, true, true, true, true);
|
|
|
|
// Close the map file
|
|
mapwad.Dispose();
|
|
|
|
// Create MapSet
|
|
bool maprestored;
|
|
if(!CreateMapSet(newmap, filepathname, options, out maprestored)) return false;
|
|
|
|
// And switch to it
|
|
ChangeMapSet(newmap);
|
|
|
|
// Sector textures may've been changed
|
|
data.UpdateUsedTextures();
|
|
|
|
// This will update DataManager.mapinfo only
|
|
data.ReloadMapInfoPartial();
|
|
|
|
// Skybox may've been changed
|
|
data.SetupSkybox();
|
|
|
|
// Update script names
|
|
LoadACS();
|
|
|
|
// Restore selection groups
|
|
options.ReadSelectionGroups();
|
|
|
|
if(General.Editing.Mode != null)
|
|
{
|
|
if(General.Editing.Mode is ClassicMode)
|
|
{
|
|
ClassicMode mode = (ClassicMode)General.Editing.Mode;
|
|
mode.OnRedoEnd();
|
|
|
|
// Center map in screen or on stored coordinates
|
|
if(options.ViewPosition.IsFinite() && !float.IsNaN(options.ViewScale))
|
|
mode.CenterOnCoordinates(options.ViewPosition, options.ViewScale);
|
|
else
|
|
mode.CenterInScreen();
|
|
}
|
|
else if(General.Editing.Mode is VisualMode)
|
|
{
|
|
VisualMode mode = (VisualMode)General.Editing.Mode;
|
|
|
|
// This will rebuild blockmap, among the other things
|
|
General.Editing.Mode.OnReloadResources();
|
|
|
|
// Update camera position
|
|
if(options.ViewPosition.IsFinite()) mode.CenterOnCoordinates(options.ViewPosition);
|
|
}
|
|
}
|
|
|
|
// Success
|
|
this.changed = maprestored;
|
|
this.maploading = false;
|
|
General.WriteLogLine("Map switching done");
|
|
General.MainWindow.UpdateMapChangedStatus();
|
|
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, tempwadreader.WadFile, 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, tempwadreader.WadFile, 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)
|
|
{
|
|
General.Plugins.OnMapSaveBegin(SavePurpose.Testing);
|
|
bool result = SaveMap(filepathname, SavePurpose.Testing);
|
|
General.Plugins.OnMapSaveEnd(SavePurpose.Testing);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This writes the map structures to the temporary file.
|
|
/// </summary>
|
|
private bool WriteMapToTempFile()
|
|
{
|
|
StatusInfo oldstatus = General.MainWindow.Status;
|
|
|
|
// 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?
|
|
if(map.Sidedefs.Count > io.MaxSidedefs)
|
|
{
|
|
// Compress sidedefs
|
|
int initialsidescount = outputset.Sidedefs.Count; //mxd
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Compressing sidedefs...");
|
|
outputset.CompressSidedefs();
|
|
|
|
// Check if it still doesnt
|
|
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!" + Environment.NewLine + Environment.NewLine
|
|
+ "Sidedefs before compresion: " + initialsidescount + Environment.NewLine
|
|
+ "Sidedefs after compresion: " + outputset.Sidedefs.Count
|
|
+ " (" + (outputset.Sidedefs.Count - io.MaxSidedefs) + " sidedefs above the limit)", MessageBoxButtons.OK);
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check things
|
|
if(map.Things.Count > io.MaxThings)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many things!", MessageBoxButtons.OK);
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
return false;
|
|
}
|
|
|
|
// Check sectors
|
|
if(map.Sectors.Count > io.MaxSectors)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many sectors!", MessageBoxButtons.OK);
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
return false;
|
|
}
|
|
|
|
// Check linedefs
|
|
if(map.Linedefs.Count > io.MaxLinedefs)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many linedefs!", MessageBoxButtons.OK);
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
return false;
|
|
}
|
|
|
|
// Check vertices
|
|
if(map.Vertices.Count > io.MaxVertices)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many vertices!", MessageBoxButtons.OK);
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
return false;
|
|
}
|
|
|
|
// TODO: Check for more limitations
|
|
|
|
// Write to temporary file
|
|
General.WriteLogLine("Writing map data structures to file...");
|
|
int index = Math.Max(0, tempwadreader.WadFile.FindLumpIndex(TEMP_MAP_HEADER));
|
|
io.Write(outputset, TEMP_MAP_HEADER, index);
|
|
outputset.Dispose();
|
|
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
return true;
|
|
}
|
|
|
|
// Initializes for an existing map
|
|
internal bool SaveMap(string newfilepathname, SavePurpose purpose)
|
|
{
|
|
string settingsfile;
|
|
WAD targetwad = null;
|
|
bool includenodes;
|
|
bool fileexists = File.Exists(newfilepathname); //mxd
|
|
|
|
General.WriteLogLine("Saving map to file: " + newfilepathname);
|
|
|
|
//mxd. Official IWAD check...
|
|
if(fileexists)
|
|
{
|
|
WAD hashtest = new WAD(newfilepathname, true);
|
|
if(hashtest.IsOfficialIWAD)
|
|
{
|
|
General.WriteLogLine("Map saving aborted: attempt to modify an official IWAD");
|
|
General.ShowErrorMessage("Official IWADs should not be modified.\nConsider making a PWAD instead", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
hashtest.Dispose();
|
|
}
|
|
|
|
// 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 or there are compiler errors (mxd)
|
|
// (not when only the map changed)
|
|
if((localscriptschanged || errors.Count > 0) && !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, false);
|
|
|
|
// Only write the map and rebuild nodes when the actual map has changed
|
|
// (not when only scripts have changed)
|
|
if(changed)
|
|
{
|
|
// Write the current map structures to the temp file
|
|
if(!WriteMapToTempFile()) return false;
|
|
|
|
// Get the corresponding nodebuilder
|
|
string nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave;
|
|
|
|
// Build the nodes
|
|
StatusInfo oldstatus = General.MainWindow.Status;
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes...");
|
|
includenodes = (!string.IsNullOrEmpty(nodebuildername) && BuildNodes(nodebuildername, true));
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
|
|
//mxd. Compress temp file...
|
|
tempwadreader.WadFile.Compress();
|
|
}
|
|
else
|
|
{
|
|
// Check if we have nodebuilder lumps
|
|
includenodes = VerifyNodebuilderLumps(tempwadreader.WadFile, TEMP_MAP_HEADER);
|
|
}
|
|
|
|
//mxd. Target file is read-only?
|
|
if(fileexists)
|
|
{
|
|
FileInfo info = new FileInfo(newfilepathname);
|
|
if(info.IsReadOnly)
|
|
{
|
|
if(General.ShowWarningMessage("Unable to save the map: target file is read-only.\nRemove read-only flag and save the map anyway?", MessageBoxButtons.YesNo) == DialogResult.Yes)
|
|
{
|
|
General.WriteLogLine("Removing read-only flag from the map file...");
|
|
try
|
|
{
|
|
info.IsReadOnly = false;
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Failed to remove read-only flag from \"" + filepathname + "\":" + Environment.NewLine + Environment.NewLine + e.Message, MessageBoxButtons.OK);
|
|
General.WriteLogLine("Failed to remove read-only flag from \"" + filepathname + "\":" + e.Message);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
General.WriteLogLine("Map saving cancelled...");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Suspend data resources
|
|
data.Suspend();
|
|
|
|
//mxd. Check if the target file is locked
|
|
if(fileexists)
|
|
{
|
|
FileLockChecker.FileLockCheckResult checkresult = FileLockChecker.CheckFile(newfilepathname);
|
|
if(!string.IsNullOrEmpty(checkresult.Error))
|
|
{
|
|
if(checkresult.Processes.Count > 0)
|
|
{
|
|
string rest = Environment.NewLine + "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 = (!string.IsNullOrEmpty(options.PreviousName) && purpose != SavePurpose.IntoFile) ? options.PreviousName : options.CurrentName;
|
|
string origwadfile = string.Empty; //mxd
|
|
int mapheaderindex = REPLACE_TARGET_MAP; //mxd. Lump index of the map file header in the source WAD
|
|
|
|
try
|
|
{
|
|
if(fileexists)
|
|
{
|
|
// 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 != string.Empty))
|
|
{
|
|
// 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) { IsIWAD = origwad.IsIWAD }; //mxd. Let's preserve wad type
|
|
|
|
// 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);
|
|
|
|
// Needed only once!
|
|
origmapconfigname = configinfo.Filename;
|
|
}
|
|
|
|
mapheaderindex = 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(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Unable to write the map to target file \"" + newfilepathname + "\":\n" + e.Message, MessageBoxButtons.OK);
|
|
if(!string.IsNullOrEmpty(origwadfile) && File.Exists(origwadfile))
|
|
{
|
|
//mxd. Clean-up
|
|
if(File.Exists(newfilepathname))
|
|
{
|
|
//mxd. We MAY've just deleted the map from the target file. Let's pretend this never happened
|
|
if(targetwad != null) targetwad.Dispose();
|
|
File.Delete(newfilepathname);
|
|
File.Move(origwadfile, newfilepathname);
|
|
}
|
|
else
|
|
{
|
|
File.Delete(origwadfile);
|
|
}
|
|
}
|
|
|
|
data.Resume();
|
|
General.WriteLogLine("Map saving failed: " + e.Message);
|
|
return false;
|
|
}
|
|
|
|
// Copy map lumps to target file
|
|
CopyLumpsByType(tempwadreader.WadFile, TEMP_MAP_HEADER, targetwad, origmapname, mapheaderindex, 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
|
|
int 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 = string.Empty;
|
|
}
|
|
|
|
// 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(true);
|
|
}
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// This stores the current structures in memory to the temporary file and rebuilds the nodes.
|
|
/// The 'nodebuildername' must be a valid nodebuilder configuration profile.
|
|
/// Returns True on success, False when failed.
|
|
/// </summary>
|
|
public bool RebuildNodes(string nodebuildername, bool failaswarning)
|
|
{
|
|
bool result;
|
|
|
|
// Write the current map structures to the temp file
|
|
if(!WriteMapToTempFile()) return false;
|
|
|
|
// Build the nodes
|
|
StatusInfo oldstatus = General.MainWindow.Status;
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes...");
|
|
if(!string.IsNullOrEmpty(nodebuildername))
|
|
result = BuildNodes(nodebuildername, failaswarning);
|
|
else
|
|
result = false;
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
|
|
//mxd. Compress temp file...
|
|
tempwadreader.WadFile.Compress();
|
|
|
|
return result;
|
|
}
|
|
|
|
// This builds the nodes in the temproary file with the given configuration name
|
|
private bool BuildNodes(string nodebuildername, bool failaswarning)
|
|
{
|
|
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 : tempwadreader.WadFile.Filename);
|
|
|
|
//mxd.
|
|
RemoveUnneededLumps(tempwadreader.WadFile, TEMP_MAP_HEADER, true);
|
|
|
|
// Copy lumps to buildwad
|
|
General.WriteLogLine("Copying map lumps to temporary build file...");
|
|
CopyLumpsByType(tempwadreader.WadFile, TEMP_MAP_HEADER, buildwad, BUILD_MAP_HEADER, REPLACE_TARGET_MAP, true, false, false, true);
|
|
|
|
// Close buildwad
|
|
buildwad.Dispose();
|
|
|
|
// Does the nodebuilder require an output file?
|
|
string tempfile2;
|
|
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, tempwadreader.WadFile, TEMP_MAP_HEADER, REPLACE_TARGET_MAP, false, false, true, false);
|
|
}
|
|
else
|
|
{
|
|
//mxd. collect errors
|
|
string compilererrors = string.Empty;
|
|
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 : string.Empty), 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 : string.Empty), MessageBoxButtons.OK);
|
|
}
|
|
|
|
// Done with the build wad
|
|
if(buildwad != null) buildwad.Dispose();
|
|
}
|
|
else //mxd
|
|
{
|
|
//collect errors
|
|
string compilererrors = string.Empty;
|
|
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();
|
|
|
|
// Let the plugins know
|
|
if(lumpscomplete) General.Plugins.OnMapNodesRebuilt();
|
|
|
|
// 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 = tempwadreader.WadFile.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 lumpdata)
|
|
{
|
|
int insertindex = tempwadreader.WadFile.Lumps.Count;
|
|
|
|
// Remove the lump if it already exists
|
|
int li = tempwadreader.WadFile.FindLumpIndex(lumpname);
|
|
if(li > -1)
|
|
{
|
|
insertindex = li;
|
|
tempwadreader.WadFile.RemoveAt(li);
|
|
}
|
|
|
|
// Insert new lump
|
|
Lump l = tempwadreader.WadFile.Insert(lumpname, insertindex, (int)lumpdata.Length);
|
|
l.Stream.Seek(0, SeekOrigin.Begin);
|
|
lumpdata.WriteTo(l.Stream);
|
|
|
|
//mxd. Mark the map as changed (will also update the title)
|
|
IsChanged = true;
|
|
}
|
|
|
|
// This checks if the specified lump exists in the temp file
|
|
public bool LumpExists(string lumpname)
|
|
{
|
|
return (tempwadreader.WadFile.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, false);
|
|
}
|
|
else
|
|
{
|
|
// Move insert index
|
|
insertindex = targetindex;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.WriteHeaders(); //mxd
|
|
}
|
|
|
|
//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. mxd. Returns the index of skipped map's header lump
|
|
private static int CopyAllLumpsExceptMap(WAD source, WAD target, GameConfiguration mapconfig, string sourcemapname)
|
|
{
|
|
// Go for all lumps
|
|
bool skipping = false;
|
|
int headerpos = REPLACE_TARGET_MAP; //mxd
|
|
for(int i = 0; i < source.Lumps.Count; i++)
|
|
{
|
|
Lump srclump = source.Lumps[i];
|
|
|
|
// 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
|
|
//TODO: I see a big, but kinda esoteric problem here if the source has several maps with the same name (mxd)
|
|
if(!skipping && headerpos == REPLACE_TARGET_MAP && srclump.Name == sourcemapname)
|
|
{
|
|
// We have encountered the map header, start skipping!
|
|
skipping = true;
|
|
headerpos = i;
|
|
}
|
|
|
|
// Not skipping this lump?
|
|
if(!skipping)
|
|
{
|
|
// Copy lump over!
|
|
Lump tgtlump = target.Insert(srclump.Name, target.Lumps.Count, srclump.Length, false);
|
|
srclump.CopyTo(tgtlump);
|
|
}
|
|
}
|
|
|
|
target.WriteHeaders(); //mxd
|
|
|
|
return headerpos;
|
|
}
|
|
|
|
// This copies specific map lumps from one WAD to another
|
|
private void CopyLumpsByType(WAD source, string sourcemapname,
|
|
WAD target, string targetmapname,
|
|
int targetheaderinsertindex, //mxd
|
|
bool copyrequired, bool copyblindcopy,
|
|
bool copynodebuild, bool copyscript)
|
|
{
|
|
// Find the map header in target (mxd. Or use the provided one)
|
|
bool replacetargetmap = (targetheaderinsertindex == REPLACE_TARGET_MAP); //mxd
|
|
int tgtheaderindex = (replacetargetmap ? target.FindLumpIndex(targetmapname) : targetheaderinsertindex); //mxd
|
|
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)
|
|
{
|
|
//mxd. Don't do this when inserting a map (SaveMap() removes the old version of the map before calling CopyLumpsByType())
|
|
if(replacetargetmap)
|
|
{
|
|
// 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, false);
|
|
lump.CopyTo(newlump);
|
|
|
|
//mxd. We still need to increment targetindex...
|
|
if(!replacetargetmap) targetindex++;
|
|
}
|
|
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.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
target.WriteHeaders(); //mxd
|
|
}
|
|
}
|
|
|
|
// 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 ================== [mxd] GZDB actions
|
|
|
|
[BeginAction("gztogglemodels")]
|
|
internal void ToggleModelsRenderingMode()
|
|
{
|
|
switch(General.Settings.GZDrawModelsMode)
|
|
{
|
|
case ModelRenderMode.NONE:
|
|
General.Settings.GZDrawModelsMode = ModelRenderMode.SELECTION;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Models rendering mode: SELECTION ONLY");
|
|
break;
|
|
|
|
case ModelRenderMode.SELECTION:
|
|
General.Settings.GZDrawModelsMode = ModelRenderMode.ACTIVE_THINGS_FILTER;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Models rendering mode: ACTIVE THINGS FILTER ONLY");
|
|
break;
|
|
|
|
case ModelRenderMode.ACTIVE_THINGS_FILTER:
|
|
General.Settings.GZDrawModelsMode = ModelRenderMode.ALL;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Models rendering mode: ALL");
|
|
break;
|
|
|
|
case ModelRenderMode.ALL:
|
|
General.Settings.GZDrawModelsMode = ModelRenderMode.NONE;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Models rendering mode: NONE");
|
|
break;
|
|
}
|
|
|
|
General.MainWindow.RedrawDisplay();
|
|
General.MainWindow.UpdateGZDoomPanel();
|
|
}
|
|
|
|
[BeginAction("gztogglelights")]
|
|
internal void ToggleLightsRenderingMode()
|
|
{
|
|
if(General.Editing.Mode is ClassicMode)
|
|
{
|
|
switch(General.Settings.GZDrawLightsMode)
|
|
{
|
|
case LightRenderMode.NONE:
|
|
General.Settings.GZDrawLightsMode = LightRenderMode.ALL;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ALL");
|
|
break;
|
|
|
|
default:
|
|
General.Settings.GZDrawLightsMode = LightRenderMode.NONE;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: NONE");
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(General.Settings.GZDrawLightsMode)
|
|
{
|
|
case LightRenderMode.NONE:
|
|
General.Settings.GZDrawLightsMode = LightRenderMode.ALL;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ALL");
|
|
break;
|
|
|
|
case LightRenderMode.ALL:
|
|
General.Settings.GZDrawLightsMode = LightRenderMode.ALL_ANIMATED;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ANIMATED");
|
|
break;
|
|
|
|
case LightRenderMode.ALL_ANIMATED:
|
|
General.Settings.GZDrawLightsMode = LightRenderMode.NONE;
|
|
General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: NONE");
|
|
break;
|
|
}
|
|
}
|
|
|
|
General.MainWindow.RedrawDisplay();
|
|
General.MainWindow.UpdateGZDoomPanel();
|
|
}
|
|
|
|
[BeginAction("gzreloadmodeldef")]
|
|
internal void ReloadModeldef()
|
|
{
|
|
data.ReloadModeldef();
|
|
}
|
|
|
|
[BeginAction("gzreloadgldefs")]
|
|
internal void ReloadGldefs()
|
|
{
|
|
data.ReloadGldefs();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Script Editing
|
|
|
|
// Show the script editor
|
|
[BeginAction("openscripteditor")]
|
|
internal void ShowScriptEditor()
|
|
{
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
// Load the window?
|
|
if(scriptwindow == null) scriptwindow = new ScriptEditorForm();
|
|
|
|
// Window not yet visible?
|
|
if(!scriptwindow.Visible) scriptwindow.Show();
|
|
|
|
scriptwindow.TopMost = General.Settings.ScriptOnTop; //mxd
|
|
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
|
|
if(scriptwindow != null)
|
|
{
|
|
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
|
|
private 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)
|
|
{
|
|
// Compile it now
|
|
success &= tempwadreader.CompileLump(lumpinfo.Name, lumpinfo.Script, errors);
|
|
}
|
|
//mxd. Is this ACS script?
|
|
else if(lumpinfo.ScriptBuild)
|
|
{
|
|
// Compile it using selected script compiler
|
|
ScriptConfiguration cfg = General.GetScriptConfiguration(ScriptType.ACS);
|
|
if(cfg != null)
|
|
{
|
|
success &= tempwadreader.CompileLump(lumpinfo.Name, cfg, errors);
|
|
}
|
|
else
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Unable to compile \"" + lumpinfo.Name + "\" script lump: unable to find suitable Script Configuration!");
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
//mxd. Update script numbers and names, collect loose ACS files.
|
|
private void LoadACS()
|
|
{
|
|
///////////////////////////////////////////
|
|
// Step 1: Update script numbers and names
|
|
///////////////////////////////////////////
|
|
|
|
General.Map.Data.ScriptResources[ScriptType.ACS] = new HashSet<ScriptResource>();
|
|
|
|
// Find SCRIPTS lump and parse it
|
|
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))
|
|
{
|
|
string error = "Unable to compile lump \"" + maplumpinfo.Name +
|
|
"\". Unable to find required script compiler configuration (\"" +
|
|
General.Map.Options.ScriptCompiler + "\").";
|
|
|
|
General.ErrorLogger.Add(ErrorType.Error, error);
|
|
return;
|
|
}
|
|
|
|
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 && scriptconfig.ScriptType == ScriptType.ACS)
|
|
{
|
|
// Get script names
|
|
AcsParserSE parser = new AcsParserSE
|
|
{
|
|
IsMapScriptsLump = true,
|
|
IgnoreErrors = true,
|
|
OnInclude = delegate(AcsParserSE se, string includefile, AcsParserSE.IncludeType includetype)
|
|
{
|
|
TextResourceData includedata = General.Map.Data.GetTextResourceData(includefile);
|
|
if(includedata == null) return false; // Fial
|
|
return se.Parse(includedata, true, includetype, false);
|
|
}
|
|
};
|
|
|
|
//INFO: CompileLump() prepends lumpname with "?" to distinguish between temporary files and files compiled in place
|
|
DataLocation location = new DataLocation { location = tempwadreader.WadFile.Filename, type = DataLocation.RESOURCE_WAD };
|
|
TextResourceData trd = new TextResourceData(stream, location, "?SCRIPTS");
|
|
if(parser.Parse(trd, scriptconfig.Compiler.Files, true, AcsParserSE.IncludeType.NONE, false))
|
|
{
|
|
// Add to text resource list
|
|
General.Map.Data.ScriptResources[parser.ScriptType].UnionWith(parser.ScriptResources.Values);
|
|
|
|
// Update the names
|
|
UpdateScriptNames(parser);
|
|
}
|
|
else
|
|
{
|
|
// Clear collections
|
|
namedscripts.Clear();
|
|
numberedscripts.Clear();
|
|
}
|
|
|
|
// Log errors, if any
|
|
if(parser.HasError) parser.LogError();
|
|
|
|
// Done here
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////
|
|
// Step 2: Try to load unused ACS files
|
|
///////////////////////////////////////////
|
|
|
|
Dictionary<string, HashSet<string>> existingacsfiles = new Dictionary<string, HashSet<string>>();
|
|
|
|
// Gather already parsed files...
|
|
foreach(ScriptResource sr in General.Map.Data.ScriptResources[ScriptType.ACS])
|
|
{
|
|
if(!existingacsfiles.ContainsKey(sr.Resource.Location.location))
|
|
existingacsfiles.Add(sr.Resource.Location.location, new HashSet<string>());
|
|
|
|
existingacsfiles[sr.Resource.Location.location].Add(sr.Filename);
|
|
}
|
|
|
|
HashSet<ScriptResource> looseacsfiles = new HashSet<ScriptResource>();
|
|
foreach(DataReader dr in General.Map.Data.Containers)
|
|
{
|
|
//TODO: implement for WADs
|
|
if(dr is PK3StructuredReader)
|
|
{
|
|
PK3StructuredReader pkr = (PK3StructuredReader)dr;
|
|
string[] acsfiles = pkr.GetFilesWithExt("", "acs", true);
|
|
foreach(string acsfile in acsfiles)
|
|
{
|
|
if(!existingacsfiles.ContainsKey(pkr.Location.location) || !existingacsfiles[pkr.Location.location].Contains(acsfile))
|
|
{
|
|
TextResourceData trd = new TextResourceData(dr, dr.LoadFile(acsfile), acsfile, true);
|
|
looseacsfiles.Add(new ScriptResource(trd, ScriptType.ACS));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to the main collection
|
|
General.Map.Data.ScriptResources[ScriptType.ACS].UnionWith(looseacsfiles);
|
|
}
|
|
|
|
//mxd
|
|
internal void UpdateScriptNames(AcsParserSE parser)
|
|
{
|
|
if(parser.HasError)
|
|
{
|
|
// Clear collections
|
|
namedscripts.Clear();
|
|
numberedscripts.Clear();
|
|
}
|
|
else
|
|
{
|
|
// Add to collections
|
|
namedscripts = new Dictionary<string, ScriptItem>(parser.NamedScripts.Count, StringComparer.OrdinalIgnoreCase);
|
|
numberedscripts = new Dictionary<int, ScriptItem>(parser.NumberedScripts.Count);
|
|
|
|
// Sort script names
|
|
parser.NamedScripts.Sort(ScriptItem.SortByName);
|
|
parser.NumberedScripts.Sort(ScriptItem.SortByIndex);
|
|
|
|
foreach(ScriptItem item in parser.NamedScripts)
|
|
{
|
|
if(!namedscripts.ContainsKey(item.Name.ToLowerInvariant()))
|
|
namedscripts.Add(item.Name.ToLowerInvariant(), item);
|
|
}
|
|
|
|
foreach(ScriptItem item in parser.NumberedScripts)
|
|
{
|
|
if(!numberedscripts.ContainsKey(item.Index))
|
|
numberedscripts.Add(item.Index, item);
|
|
}
|
|
}
|
|
}
|
|
|
|
//mxd
|
|
public bool ScriptNumberExists(int scriptnumber)
|
|
{
|
|
return numberedscripts.ContainsKey(scriptnumber);
|
|
}
|
|
|
|
//mxd
|
|
public bool ScriptNameExists(string scriptname)
|
|
{
|
|
return namedscripts.ContainsKey(scriptname);
|
|
}
|
|
|
|
#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(true);
|
|
|
|
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(bool clearerrors) //mxd. clearerrors flag
|
|
{
|
|
// 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?
|
|
if(clearerrors) 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, options.StrictPatches, 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();
|
|
|
|
//mxd. Also Check appropriate button on interface
|
|
General.MainWindow.CheckEditModeButton(General.Editing.Mode.EditModeButtonName);
|
|
}
|
|
|
|
//mxd. Update script names
|
|
LoadACS();
|
|
|
|
//mxd. Script Editor may need updating...
|
|
if(scriptwindow != null) scriptwindow.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, false);
|
|
if(optionsform.ShowDialog(General.MainWindow) == DialogResult.OK)
|
|
{
|
|
// Update interface
|
|
//General.MainWindow.UpdateInterface();
|
|
|
|
// Stop data manager
|
|
data.Dispose();
|
|
|
|
//mxd. Clear errors
|
|
General.ErrorLogger.Clear();
|
|
|
|
// 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
|
|
|
|
//mxd. Step 1 of hackish way to translate SP/MP thing flags to Hexen / UDMF formats...
|
|
//TODO: add proper Doom -> Hexen thing flags translation to the configs?
|
|
if(oldiotype == typeof(DoomMapSetIO) && configinfo.FormatInterface != "doommapsetio")
|
|
{
|
|
// Translate to UDMF using Doom things flags translation table
|
|
foreach(Thing t in General.Map.Map.Things) t.TranslateToUDMF();
|
|
}
|
|
|
|
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, tempwadreader.WadFile, this);
|
|
|
|
//mxd. Some lumps may've become unneeded during map format conversion.
|
|
if(oldiotype != io.GetType())
|
|
RemoveUnneededLumps(tempwadreader.WadFile, TEMP_MAP_HEADER, false);
|
|
|
|
// Create required lumps if they don't exist yet
|
|
CreateRequiredLumps(tempwadreader.WadFile, 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(DoomMapSetIO))
|
|
{
|
|
if(io is UniversalMapSetIO)
|
|
{
|
|
//Thing flags were already translated in Setp 1...
|
|
//TODO: linedef actions will require the same handling...
|
|
foreach(Linedef l in General.Map.Map.Linedefs) l.TranslateToUDMF(oldiotype);
|
|
}
|
|
else if(io is HexenMapSetIO)
|
|
{
|
|
// Step 2 of hackish way to translate SP/MP thing flags to Hexen map format...
|
|
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();
|
|
}
|
|
|
|
// Drop all arguments
|
|
if(oldiotype != typeof(DoomMapSetIO) && io is DoomMapSetIO)
|
|
{
|
|
|
|
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();
|
|
|
|
// Reload resources
|
|
ReloadResources(false);
|
|
|
|
// Update interface
|
|
General.MainWindow.SetupInterface();
|
|
General.MainWindow.UpdateThingsFilters();
|
|
General.MainWindow.UpdateLinedefColorPresets(); //mxd
|
|
General.MainWindow.UpdateInterface();
|
|
|
|
// 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
|
|
[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
|
|
foreach(Vertex v in movedVerts)
|
|
{
|
|
BlockEntry 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
|
|
}
|
|
} |