mirror of
https://git.do.srb2.org/STJr/ZoneBuilder.git
synced 2024-11-15 00:51:39 +00:00
2822 lines
No EOL
94 KiB
C#
2822 lines
No EOL
94 KiB
C#
|
|
#region ================== Copyright (c) 2007 Pascal vd Heiden
|
|
|
|
/*
|
|
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
|
|
* This program is released under GNU General Public License
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#endregion
|
|
|
|
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using CodeImp.DoomBuilder.Actions;
|
|
using CodeImp.DoomBuilder.Compilers;
|
|
using CodeImp.DoomBuilder.Config;
|
|
using CodeImp.DoomBuilder.Data;
|
|
using CodeImp.DoomBuilder.Editing;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using CodeImp.DoomBuilder.GZBuilder.Data; //mxd
|
|
using CodeImp.DoomBuilder.ZDoom; //mxd
|
|
using CodeImp.DoomBuilder.IO;
|
|
using CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.VisualModes;
|
|
using CodeImp.DoomBuilder.Windows;
|
|
using System.Collections;
|
|
|
|
#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 WAD tempwad;
|
|
private GridSetup grid;
|
|
private UndoManager undoredo;
|
|
private CopyPasteManager copypaste;
|
|
private Launcher launcher;
|
|
private ThingsFilter thingsfilter;
|
|
private ScriptEditorForm scriptwindow;
|
|
private List<CompilerError> errors;
|
|
private VisualCamera visualcamera;
|
|
|
|
//mxd
|
|
private Dictionary<string, ScriptItem> namedscripts;
|
|
private Dictionary<int, ScriptItem> numberedscripts;
|
|
private readonly HashSet<string> scriptincludes;
|
|
|
|
// Disposing
|
|
private bool isdisposed;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public string FilePathName { get { return filepathname; } }
|
|
public string FileTitle { get { return filetitle; } internal set { filetitle = value; } } //mxd. Added setter
|
|
public string TempPath { get { return temppath; } }
|
|
public MapOptions Options { get { return options; } }
|
|
public MapSet Map { get { return map; } }
|
|
public DataManager Data { get { return data; } }
|
|
public bool IsChanged { get { return changed | CheckScriptChanged(); } set { changed |= value; if(!maploading) General.MainWindow.UpdateMapChangedStatus(); } }
|
|
public bool IsDisposed { get { return isdisposed; } }
|
|
internal D3DDevice Graphics { get { return graphics; } }
|
|
public IRenderer2D Renderer2D { get { return renderer2d; } }
|
|
public IRenderer3D Renderer3D { get { return renderer3d; } }
|
|
internal Renderer2D CRenderer2D { get { return renderer2d; } }
|
|
internal Renderer3D CRenderer3D { get { return renderer3d; } }
|
|
public GameConfiguration Config { get { return config; } }
|
|
public ConfigurationInfo ConfigSettings { get { return configinfo; } }
|
|
public GridSetup Grid { get { return grid; } }
|
|
public UndoManager UndoRedo { get { return undoredo; } }
|
|
internal CopyPasteManager CopyPaste { get { return copypaste; } }
|
|
public IMapSetIO FormatInterface { get { return io; } }
|
|
internal Launcher Launcher { get { return launcher; } }
|
|
public ThingsFilter ThingsFilter { get { return thingsfilter; } }
|
|
internal List<CompilerError> Errors { get { return errors; } }
|
|
internal ScriptEditorForm ScriptEditor { get { return scriptwindow; } }
|
|
public VisualCamera VisualCamera { get { return visualcamera; } set { visualcamera = value; } }
|
|
public bool IsScriptsWindowOpen { get { return (scriptwindow != null) && !scriptwindow.IsDisposed; } }
|
|
|
|
//mxd. Map format
|
|
public bool UDMF { get { return config.UDMF; } }
|
|
public bool HEXEN { get { return config.HEXEN; } }
|
|
public bool DOOM { get { return config.DOOM; } }
|
|
public bool SRB2 { get { return config.SRB2; } }
|
|
|
|
//mxd. Scripts
|
|
internal Dictionary<string, ScriptItem> NamedScripts { get { return namedscripts; } }
|
|
internal Dictionary<int, ScriptItem> NumberedScripts { get { return numberedscripts; } }
|
|
internal HashSet<string> ScriptIncludes { get { return scriptincludes; } }
|
|
|
|
public ViewMode ViewMode { get { return renderer2d.ViewMode; } }
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor / Disposer
|
|
|
|
// Constructor
|
|
internal MapManager()
|
|
{
|
|
// We have no destructor
|
|
GC.SuppressFinalize(this);
|
|
|
|
// Create temporary path
|
|
temppath = General.MakeTempDirname();
|
|
Directory.CreateDirectory(temppath);
|
|
General.WriteLogLine("Temporary directory: " + temppath);
|
|
|
|
// Basic objects
|
|
grid = new GridSetup();
|
|
undoredo = new UndoManager();
|
|
copypaste = new CopyPasteManager();
|
|
launcher = new Launcher(this);
|
|
thingsfilter = new NullThingsFilter();
|
|
errors = new List<CompilerError>();
|
|
|
|
//mxd
|
|
numberedscripts = new Dictionary<int, ScriptItem>();
|
|
namedscripts = new Dictionary<string, ScriptItem>();
|
|
scriptincludes = new HashSet<string>();
|
|
}
|
|
|
|
// Disposer
|
|
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(tempwad != null) tempwad.Dispose();
|
|
General.WriteLogLine("Unloading map data...");
|
|
if(map != null) map.Dispose();
|
|
General.WriteLogLine("Stopping graphics device...");
|
|
if(renderer2d != null) renderer2d.Dispose();
|
|
if(renderer3d != null) renderer3d.Dispose();
|
|
if(graphics != null) graphics.Dispose();
|
|
visualcamera = null;
|
|
grid = null;
|
|
launcher = null;
|
|
copypaste = null;
|
|
undoredo = null;
|
|
data = null;
|
|
tempwad = null;
|
|
map = null;
|
|
renderer2d = null;
|
|
renderer3d = null;
|
|
graphics = null;
|
|
|
|
// We may spend some time to clean things up here
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers(); //mxd
|
|
GC.Collect(); //mxd
|
|
|
|
// Remove temp file
|
|
General.WriteLogLine("Removing temporary directory...");
|
|
try
|
|
{
|
|
Directory.Delete(temppath, true);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
General.WriteLogLine(e.GetType().Name + ": " + e.Message);
|
|
General.WriteLogLine("Failed to remove temporary directory!");
|
|
}
|
|
|
|
// Let the plugins know
|
|
General.Plugins.OnMapCloseEnd();
|
|
|
|
// Done
|
|
isdisposed = true;
|
|
}
|
|
}
|
|
|
|
#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();
|
|
|
|
// Create map data
|
|
map = new MapSet();
|
|
|
|
// Create temp wadfile
|
|
string tempfile = General.MakeTempFilename(temppath);
|
|
General.WriteLogLine("Creating temporary file: " + tempfile);
|
|
#if DEBUG
|
|
tempwad = new WAD(tempfile);
|
|
#else
|
|
try { tempwad = new WAD(tempfile); }
|
|
catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Read the map from temp file
|
|
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
|
|
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
|
|
|
|
// Create required lumps
|
|
General.WriteLogLine("Creating map data structures...");
|
|
tempwad.Insert(TEMP_MAP_HEADER, 0, 0);
|
|
io.Write(map, TEMP_MAP_HEADER, 1);
|
|
CreateRequiredLumps(tempwad, TEMP_MAP_HEADER);
|
|
|
|
// Load data manager
|
|
General.WriteLogLine("Loading data resources...");
|
|
data = new DataManager();
|
|
data.Load(configinfo.Resources, options.Resources);
|
|
|
|
// Update structures
|
|
options.ApplyGridSettings();
|
|
map.UpdateConfiguration();
|
|
map.Update();
|
|
thingsfilter.Update();
|
|
|
|
namedscripts = new Dictionary<string, ScriptItem>(); //mxd
|
|
numberedscripts = new Dictionary<int, ScriptItem>(); //mxd
|
|
|
|
// Bind any methods
|
|
General.Actions.BindMethods(this);
|
|
|
|
// Set defaults
|
|
this.visualcamera = new VisualCamera();
|
|
General.Editing.ChangeMode(configinfo.StartMode);
|
|
ClassicMode cmode = (General.Editing.Mode as ClassicMode);
|
|
if(cmode != null) cmode.SetZoom(0.5f);
|
|
renderer2d.SetViewMode((ViewMode)General.Settings.DefaultViewMode);
|
|
General.Settings.SetDefaultThingFlags(config.DefaultThingFlags);
|
|
|
|
// Success
|
|
this.changed = false;
|
|
this.maploading = false; //mxd
|
|
General.WriteLogLine("Map creation done");
|
|
General.MainWindow.UpdateMapChangedStatus(); //mxd
|
|
return true;
|
|
}
|
|
|
|
// Initializes for an existing map
|
|
internal bool InitializeOpenMap(string filepathname, MapOptions options)
|
|
{
|
|
WAD mapwad;
|
|
|
|
#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
|
|
string tempfile = General.MakeTempFilename(temppath);
|
|
General.WriteLogLine("Creating temporary file: " + tempfile);
|
|
#if DEBUG
|
|
tempwad = new WAD(tempfile);
|
|
#else
|
|
try { tempwad = new WAD(tempfile); } catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Now open the map file
|
|
General.WriteLogLine("Opening source file: " + filepathname);
|
|
#if DEBUG
|
|
mapwad = new WAD(filepathname, true);
|
|
#else
|
|
try { mapwad = new WAD(filepathname, true); } catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while opening source wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Copy the map lumps to the temp file
|
|
General.WriteLogLine("Copying map lumps to temporary file...");
|
|
CopyLumpsByType(mapwad, options.CurrentName, tempwad, TEMP_MAP_HEADER, true, true, true, true);
|
|
|
|
// Close the map file
|
|
mapwad.Dispose();
|
|
|
|
//mxd. Create MapSet
|
|
bool maprestored;
|
|
if(!CreateMapSet(map, filepathname, options, out maprestored)) return false;
|
|
|
|
// Load data manager
|
|
General.WriteLogLine("Loading data resources...");
|
|
data = new DataManager();
|
|
DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false);
|
|
data.Load(configinfo.Resources, options.Resources, maplocation);
|
|
|
|
// Remove unused sectors
|
|
map.RemoveUnusedSectors(true);
|
|
|
|
//mxd. Translate to long or short texture names.
|
|
bool nameschanged = map.TranslateTextureNames(config.UseLongTextureNames, false);
|
|
grid.TranslateBackgroundName(config.UseLongTextureNames);
|
|
|
|
//mxd. Sector textures may've been changed
|
|
if(nameschanged) data.UpdateUsedTextures();
|
|
|
|
//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 includes list and script names
|
|
UpdateScriptNames(true);
|
|
|
|
//mxd. Restore selection groups
|
|
options.ReadSelectionGroups();
|
|
|
|
// Bind any methods
|
|
General.Actions.BindMethods(this);
|
|
|
|
// Set defaults
|
|
this.visualcamera = new VisualCamera();
|
|
General.Editing.ChangeMode(configinfo.StartMode);
|
|
renderer2d.SetViewMode((ViewMode)General.Settings.DefaultViewMode);
|
|
General.Settings.SetDefaultThingFlags(config.DefaultThingFlags);
|
|
|
|
// Center map in screen
|
|
//if(General.Editing.Mode is ClassicMode) (General.Editing.Mode as ClassicMode).CenterInScreen();
|
|
|
|
// Success
|
|
this.changed = maprestored; //mxd
|
|
this.maploading = false; //mxd
|
|
General.WriteLogLine("Map loading done");
|
|
General.MainWindow.UpdateMapChangedStatus(); //mxd
|
|
return true;
|
|
}
|
|
|
|
//mxd. This switches to another map in the same wad
|
|
internal bool InitializeSwitchMap(MapOptions options)
|
|
{
|
|
#if DEBUG
|
|
DebugConsole.Clear();
|
|
#endif
|
|
|
|
this.changed = false;
|
|
this.maploading = true;
|
|
this.options = options;
|
|
|
|
// Create map data
|
|
MapSet newmap = new MapSet();
|
|
WAD mapwad;
|
|
|
|
// Create temp wadfile
|
|
string tempfile = General.MakeTempFilename(temppath);
|
|
General.WriteLogLine("Creating temporary file: " + tempfile);
|
|
if(tempwad != null) tempwad.Dispose();
|
|
|
|
#if DEBUG
|
|
tempwad = new WAD(tempfile);
|
|
#else
|
|
try { tempwad = new WAD(tempfile); } catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Now open the map file
|
|
General.WriteLogLine("Opening source file: " + filepathname);
|
|
|
|
#if DEBUG
|
|
mapwad = new WAD(filepathname, true);
|
|
#else
|
|
try { mapwad = new WAD(filepathname, true); } catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while opening source wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Copy the map lumps to the temp file
|
|
General.WriteLogLine("Copying map lumps to temporary file...");
|
|
CopyLumpsByType(mapwad, options.CurrentName, tempwad, TEMP_MAP_HEADER, true, true, true, true);
|
|
|
|
// Close the map file
|
|
mapwad.Dispose();
|
|
|
|
// Create MapSet
|
|
bool maprestored;
|
|
if(!CreateMapSet(newmap, filepathname, options, out maprestored)) return false;
|
|
|
|
// And switch to it
|
|
ChangeMapSet(newmap);
|
|
|
|
// Translate texture names
|
|
map.TranslateTextureNames(config.UseLongTextureNames, false);
|
|
grid.TranslateBackgroundName(config.UseLongTextureNames);
|
|
|
|
// Sector textures may've been changed
|
|
data.UpdateUsedTextures();
|
|
|
|
// This will update DataManager.mapinfo only
|
|
data.ReloadMapInfoPartial();
|
|
|
|
// Skybox may've been changed
|
|
data.SetupSkybox();
|
|
|
|
// Update includes list and script names
|
|
UpdateScriptNames(true);
|
|
|
|
// 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, tempwad, this);
|
|
General.WriteLogLine("Restoring map from '" + backuppath + "'...");
|
|
|
|
#if DEBUG
|
|
// Restore map
|
|
newmap.Deserialize(SharpCompressHelper.DecompressStream(new MemoryStream(File.ReadAllBytes(backuppath))));
|
|
#else
|
|
try
|
|
{
|
|
// Restore map
|
|
newmap.Deserialize(SharpCompressHelper.DecompressStream(new MemoryStream(File.ReadAllBytes(backuppath))));
|
|
|
|
// Delete the backup
|
|
File.Delete(backuppath);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Unable to restore the map data structures from the backup. " + e.GetType().Name + ": " + e.Message);
|
|
General.ShowErrorMessage("Unable to restore the map data structures from the backup.", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
maprestored = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the map from temp file
|
|
if(!maprestored)
|
|
{
|
|
newmap.BeginAddRemove();
|
|
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
|
|
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
|
|
General.WriteLogLine("Reading map data from file...");
|
|
#if DEBUG
|
|
newmap = io.Read(newmap, TEMP_MAP_HEADER);
|
|
#else
|
|
try { newmap = io.Read(newmap, TEMP_MAP_HEADER); } catch(Exception e)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Unable to read the map data with the specified configuration. " + e.GetType().Name + ": " + e.Message);
|
|
General.ShowErrorMessage("Unable to read the map data with the specified configuration." + Environment.NewLine + Environment.NewLine + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
newmap.EndAddRemove();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Save
|
|
|
|
/// <summary>
|
|
/// This exports the structures from memory into a WAD file with the current map format.
|
|
/// </summary>
|
|
public bool ExportToFile(string filepathname)
|
|
{
|
|
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, tempwad.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);
|
|
|
|
// 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);
|
|
}
|
|
else
|
|
{
|
|
// Check if we have nodebuilder lumps
|
|
includenodes = VerifyNodebuilderLumps(tempwad, 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
|
|
|
|
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 = FindAndRemoveMap(wad, origmapname, false);
|
|
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;
|
|
}
|
|
}
|
|
|
|
int backups = General.Settings.MaxBackups;
|
|
if (backups > 0)
|
|
{
|
|
// Backup existing file, if any
|
|
if (File.Exists(newfilepathname + ".backup" + backups)) File.Delete(newfilepathname + ".backup" + backups);
|
|
for (int i = backups - 1; i >= 1; i--)
|
|
{
|
|
if (File.Exists(newfilepathname + ".backup" + i)) File.Move(newfilepathname + ".backup" + i, newfilepathname + ".backup" + (i+1));
|
|
}
|
|
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);
|
|
}
|
|
|
|
// [ZZ] We really want to tell apart saving into current archive from saving into a new one.
|
|
// Treat "save as" into the current archive as normal save.
|
|
bool isSaveAs = (purpose == SavePurpose.AsNewFile) && (newfilepathname != filepathname);
|
|
// On Save AS we have to copy the previous file to the new file
|
|
if(isSaveAs)
|
|
{
|
|
// 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
|
|
// [ZZ] The original code here would do some weird trickery with tempfiles.
|
|
// I'm just finding the map lumps and deleting them on later stages.
|
|
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(tempwad, TEMP_MAP_HEADER, targetwad, origmapname, true, true, includenodes, true, 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();
|
|
}
|
|
|
|
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);
|
|
|
|
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 : tempwad.Filename);
|
|
|
|
//mxd.
|
|
RemoveUnneededLumps(tempwad, TEMP_MAP_HEADER, true);
|
|
|
|
// Copy lumps to buildwad
|
|
General.WriteLogLine("Copying map lumps to temporary build file...");
|
|
CopyLumpsByType(tempwad, TEMP_MAP_HEADER, buildwad, BUILD_MAP_HEADER, true, false, false, true);
|
|
|
|
// Close buildwad
|
|
buildwad.Dispose();
|
|
|
|
// Does the nodebuilder require an output file?
|
|
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, tempwad, TEMP_MAP_HEADER, 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 = tempwad.FindLump(lumpname);
|
|
if(l != null)
|
|
{
|
|
l.Stream.Seek(0, SeekOrigin.Begin);
|
|
return new MemoryStream(l.Stream.ReadAllBytes());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// This writes a copy of the data to a lump in the temp file
|
|
public void SetLumpData(string lumpname, MemoryStream lumpdata)
|
|
{
|
|
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)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 (tempwad.FindLumpIndex(lumpname) > -1);
|
|
}
|
|
|
|
// This creates empty lumps for those required
|
|
private void CreateRequiredLumps(WAD target, string mapname)
|
|
{
|
|
// Find the map header in target
|
|
int headerindex = target.FindLumpIndex(mapname);
|
|
if(headerindex == -1)
|
|
{
|
|
// If this header doesnt exists in the target
|
|
// then insert at the end of the target
|
|
headerindex = target.Lumps.Count;
|
|
}
|
|
|
|
// Begin inserting at target header index
|
|
int insertindex = headerindex;
|
|
|
|
// Go for all the map lump names
|
|
foreach(KeyValuePair<string, MapLumpInfo> group in config.MapLumps)
|
|
{
|
|
// Check if this lump is required
|
|
if(group.Value.Required)
|
|
{
|
|
// Get the lump name
|
|
string lumpname = (group.Key.Contains(CONFIG_MAP_HEADER) ? group.Key.Replace(CONFIG_MAP_HEADER, mapname) : group.Key); //mxd
|
|
|
|
// Check if the lump is missing at the target
|
|
int targetindex = FindSpecificLump(target, lumpname, headerindex, mapname, config.MapLumps);
|
|
if(targetindex == -1)
|
|
{
|
|
// Determine target index
|
|
insertindex++;
|
|
if(insertindex > target.Lumps.Count) insertindex = target.Lumps.Count;
|
|
|
|
// Create new, emtpy lump
|
|
General.WriteLogLine(lumpname + " is required! Created empty lump.");
|
|
target.Insert(lumpname, insertindex, 0, 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;
|
|
|
|
//SRB2: Skip BLOCKMAP and REJECT to prevent outdated data from persisting.
|
|
if (General.Map.SRB2 && (group.Key.ToUpperInvariant() == "BLOCKMAP" || group.Key.ToUpperInvariant() == "REJECT")) 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);
|
|
}
|
|
|
|
// [ZZ] this is the more specific case of MatchConfiguration from OpenMapOptionsForm.
|
|
// that code is not possible to reuse for this purpose though.
|
|
private int FindAndRemoveMap(WAD target, string targetmapname, bool reallyremove)
|
|
{
|
|
int tgtheaderindex = -1;
|
|
int lastindex = -1;
|
|
|
|
bool writeheaders = false;
|
|
// [ZZ] Detect and delete target map from the target archive. Note that it can be in different format compared to current one, and there can be multiple maps with the same name as well.
|
|
//
|
|
while (true)
|
|
{
|
|
int nextindex = target.FindLumpIndex(targetmapname, lastindex+1);
|
|
// note that the original algorithm would break if you have a patch or a texture named MAP01 for example...
|
|
// this is the case for multiple megawads that have level selection screen (Duel40-style), but luckily most of them are using the PK3 format.
|
|
// we are trying to detect if this is a valid map and shouldn't try to open or save over something that uses the map name as well.
|
|
if (nextindex < 0) break; // next lump not found
|
|
lastindex = nextindex;
|
|
|
|
// try to detect the format used for this map.
|
|
// if more than one format matches, do... idk what actually.
|
|
// todo: move this code out and call it something like DetectMapConfiguration
|
|
List<List<string>> trylists = new List<List<string>>();
|
|
foreach (ConfigurationInfo cinfo in General.Configs)
|
|
{
|
|
List<string> maplumps = new List<string>();
|
|
// parse only the map lumps section of the config.
|
|
Configuration cfg = cinfo.Configuration;
|
|
IDictionary dic = cfg.ReadSetting("maplumpnames", new Hashtable());
|
|
foreach (string k in dic.Keys)
|
|
maplumps.Add(k);
|
|
|
|
// check if we already have this lump list. don't duplicate.
|
|
bool found = false;
|
|
foreach (List<string> ctrylist in trylists)
|
|
{
|
|
if (ctrylist.Count == maplumps.Count &&
|
|
ctrylist.SequenceEqual(maplumps))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
trylists.Add(maplumps);
|
|
}
|
|
|
|
// currently outputs 6 in 43. seems to work correctly.
|
|
General.WriteLogLine(string.Format("Found {0} different map lump lists in the {1} registered game configurations", trylists.Count, General.Configs.Count));
|
|
|
|
// find the most probable lump list.
|
|
int maxmatches = 0;
|
|
List<string> trylist = null;
|
|
foreach (List<string> lst in trylists)
|
|
{
|
|
int matches = 0;
|
|
int maxcnt = lst.Count;
|
|
int checkindex = nextindex+1;
|
|
foreach (string lmp in lst)
|
|
{
|
|
if (checkindex >= target.Lumps.Count)
|
|
break;
|
|
bool match = lst.Contains(target.Lumps[checkindex].Name);
|
|
if (match) matches++;
|
|
else break; // stop matching on first non-matching lump
|
|
checkindex++;
|
|
}
|
|
|
|
if (matches > maxmatches)
|
|
{
|
|
trylist = lst;
|
|
maxmatches = matches;
|
|
}
|
|
}
|
|
|
|
// if we didn't find anything it's weird...
|
|
if (trylist != null)
|
|
{
|
|
if (reallyremove)
|
|
{
|
|
int checkindex = nextindex + 1;
|
|
foreach (string lmp in trylist)
|
|
{
|
|
if (checkindex >= target.Lumps.Count)
|
|
break;
|
|
bool match = trylist.Contains(target.Lumps[checkindex].Name);
|
|
if (match) target.RemoveAt(checkindex);
|
|
else break; // stop deleting on first non-matching lump
|
|
}
|
|
|
|
// now if trylist was not null, we need to remove the header as well.
|
|
target.RemoveAt(nextindex, false);
|
|
writeheaders = true;
|
|
lastindex--;
|
|
}
|
|
|
|
tgtheaderindex = nextindex;
|
|
}
|
|
}
|
|
|
|
if (writeheaders) target.WriteHeaders();
|
|
return tgtheaderindex;
|
|
}
|
|
|
|
// 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 deleteold = false) // [ZZ] deleteold=true means we need to delete the old map in-place. we only call this in SaveMap()
|
|
{
|
|
int tgtheaderindex = FindAndRemoveMap(target, targetmapname, deleteold); // this is unobvious, but this function also performs search, not just deletion :)
|
|
bool replacetargetmap = !(deleteold && (tgtheaderindex > -1)); // this is unobvious, but we set replacetargetmap to false when we are completely overwriting it
|
|
|
|
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 = FindAndRemoveMap(source, sourcemapname, false);
|
|
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;
|
|
|
|
if(scriptwindow == null)
|
|
{
|
|
// Load the window
|
|
scriptwindow = new ScriptEditorForm();
|
|
}
|
|
|
|
// Window not yet visible?
|
|
if(!scriptwindow.Visible)
|
|
{
|
|
// Show the window
|
|
if(General.Settings.ScriptOnTop)
|
|
{
|
|
if(scriptwindow.Visible && (scriptwindow.Owner == null)) scriptwindow.Hide();
|
|
scriptwindow.Show(General.MainWindow);
|
|
}
|
|
else
|
|
{
|
|
if(scriptwindow.Visible && (scriptwindow.Owner != null)) scriptwindow.Hide();
|
|
scriptwindow.Show();
|
|
}
|
|
}
|
|
|
|
if(scriptwindow.WindowState == FormWindowState.Minimized) scriptwindow.WindowState = FormWindowState.Normal; //mxd
|
|
scriptwindow.Activate();
|
|
scriptwindow.Focus();
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
|
|
// This asks the user to save changes in script files
|
|
// Returns false when cancelled by the user
|
|
internal bool AskSaveScriptChanges()
|
|
{
|
|
// Window open?
|
|
if(scriptwindow != null)
|
|
{
|
|
// Ask to save changes
|
|
// This also saves implicitly
|
|
return scriptwindow.AskSaveAll();
|
|
}
|
|
|
|
// No problems
|
|
return true;
|
|
}
|
|
|
|
// This applies the changed status for internal scripts
|
|
internal void ApplyScriptChanged()
|
|
{
|
|
// Remember if lumps are changed
|
|
scriptschanged |= scriptwindow.Editor.CheckImplicitChanges();
|
|
}
|
|
|
|
// Close the script editor
|
|
// Specify true for the closing parameter when
|
|
// the window is already in the closing process
|
|
internal void CloseScriptEditor(bool closing)
|
|
{
|
|
if(scriptwindow != null)
|
|
{
|
|
if(!scriptwindow.IsDisposed)
|
|
{
|
|
// Remember what files were open
|
|
scriptwindow.Editor.WriteOpenFilesToConfiguration();
|
|
|
|
// Close now
|
|
if(!closing) scriptwindow.Close();
|
|
}
|
|
|
|
// Done
|
|
scriptwindow = null;
|
|
}
|
|
}
|
|
|
|
// This checks if the scripts are changed
|
|
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 || lumpinfo.ScriptBuild)
|
|
{
|
|
// Compile it now
|
|
success &= CompileLump(lumpinfo.Name, false);
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// This compiles a script lump and returns any errors that may have occurred
|
|
// Returns true when our code worked properly (even when the compiler returned errors)
|
|
internal bool CompileLump(string lumpname, bool clearerrors)
|
|
{
|
|
//mxd. Boilerplate
|
|
if(!config.MapLumps.ContainsKey(lumpname))
|
|
{
|
|
General.ShowErrorMessage("Unable to compile lump '" + lumpname + "'. This lump is not defined in the current game configuration.", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
string inputfile;
|
|
Compiler compiler;
|
|
string reallumpname = lumpname;
|
|
|
|
//mxd. Does lump require compiling?
|
|
ScriptConfiguration scriptconfig;
|
|
if(config.MapLumps[lumpname].ScriptBuild)
|
|
{
|
|
//mxd. More boilderplate
|
|
if(!General.CompiledScriptConfigs.ContainsKey(General.Map.Options.ScriptCompiler))
|
|
{
|
|
General.ShowErrorMessage("Unable to compile lump '" + lumpname + "'. Unable to find required script compiler configuration ('" + General.Map.Options.ScriptCompiler + "').", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
scriptconfig = General.CompiledScriptConfigs[General.Map.Options.ScriptCompiler];
|
|
}
|
|
else
|
|
{
|
|
scriptconfig = config.MapLumps[lumpname].Script;
|
|
}
|
|
if(scriptconfig.Compiler == null) return true;
|
|
|
|
// Find the lump
|
|
if(lumpname == CONFIG_MAP_HEADER) reallumpname = TEMP_MAP_HEADER;
|
|
Lump lump = tempwad.FindLump(reallumpname);
|
|
if(lump == null) throw new Exception("No such lump in temporary wad file '" + reallumpname + "'.");
|
|
|
|
// Determine source file
|
|
string sourcefile = (filepathname.Length > 0 ? filepathname : tempwad.Filename);
|
|
|
|
// New list of errors
|
|
if(clearerrors) errors.Clear();
|
|
|
|
// Determine the script configuration to use
|
|
try
|
|
{
|
|
// Initialize compiler
|
|
compiler = scriptconfig.Compiler.Create();
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Fail
|
|
errors.Add(new CompilerError("Unable to initialize compiler. " + e.GetType().Name + ": " + e.Message));
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Write lump data to temp script file in compiler's temp directory
|
|
inputfile = General.MakeTempFilename(compiler.Location, "tmp");
|
|
lump.Stream.Seek(0, SeekOrigin.Begin);
|
|
BinaryReader reader = new BinaryReader(lump.Stream);
|
|
File.WriteAllBytes(inputfile, reader.ReadBytes((int)lump.Stream.Length));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Fail
|
|
compiler.Dispose();
|
|
errors.Add(new CompilerError("Unable to write script to working file. " + e.GetType().Name + ": " + e.Message));
|
|
return false;
|
|
}
|
|
|
|
// Make random output filename
|
|
string outputfile = General.MakeTempFilename(compiler.Location, "tmp");
|
|
|
|
// Run compiler
|
|
compiler.Parameters = scriptconfig.Parameters;
|
|
compiler.InputFile = Path.GetFileName(inputfile);
|
|
compiler.OutputFile = Path.GetFileName(outputfile);
|
|
compiler.SourceFile = sourcefile;
|
|
compiler.WorkingDirectory = Path.GetDirectoryName(inputfile);
|
|
|
|
//mxd
|
|
if(scriptconfig.ScriptType == ScriptType.ACS)
|
|
{
|
|
compiler.Includes = General.Map.ScriptIncludes;
|
|
compiler.CopyIncludesToWorkingDirectory = true;
|
|
}
|
|
|
|
if(compiler.Run())
|
|
{
|
|
// Process errors
|
|
foreach(CompilerError e in compiler.Errors)
|
|
{
|
|
CompilerError newerror = e;
|
|
|
|
// If the error's filename equals our temporary file,
|
|
// use the lump name instead and prefix it with ?
|
|
if(string.Compare(e.filename, inputfile, true) == 0)
|
|
newerror.filename = "?" + reallumpname;
|
|
|
|
errors.Add(newerror);
|
|
}
|
|
|
|
// No errors?
|
|
if(compiler.Errors.Length == 0)
|
|
{
|
|
// Output file exists?
|
|
if(File.Exists(outputfile))
|
|
{
|
|
// Copy output file data into a lump?
|
|
if(!string.IsNullOrEmpty(scriptconfig.ResultLump))
|
|
{
|
|
// Do that now then
|
|
byte[] filedata;
|
|
|
|
try
|
|
{
|
|
filedata = File.ReadAllBytes(outputfile);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Fail
|
|
compiler.Dispose();
|
|
errors.Add(new CompilerError("Unable to read compiler output file. " + e.GetType().Name + ": " + e.Message));
|
|
return false;
|
|
}
|
|
|
|
// Store data
|
|
MemoryStream stream = new MemoryStream(filedata);
|
|
SetLumpData(scriptconfig.ResultLump, stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
compiler.Dispose();
|
|
|
|
// Done
|
|
return true;
|
|
}
|
|
|
|
// Fail
|
|
compiler.Dispose();
|
|
errors.Clear(); //mxd
|
|
return false;
|
|
}
|
|
|
|
// This clears all compiler errors
|
|
/*internal void ClearCompilerErrors()
|
|
{
|
|
errors.Clear();
|
|
}*/
|
|
|
|
//mxd. Update includes list and script names
|
|
internal List<CompilerError> UpdateScriptNames(bool logerrors)
|
|
{
|
|
List<CompilerError> compilererrors = UpdateScriptNames();
|
|
if(logerrors && compilererrors.Count > 0)
|
|
{
|
|
//INFO: CompileLump() prepends lumpname with "?" to distinguish between temporary files and files compiled in place
|
|
//INFO: also, error.linenumber is zero-based
|
|
foreach(CompilerError error in compilererrors)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "ACS error in '" + (error.filename.StartsWith("?") ? error.filename.Replace("?", String.Empty) : error.filename)
|
|
+ (error.linenumber != CompilerError.NO_LINE_NUMBER ? "', line " + (error.linenumber + 1) : "'")
|
|
+ ". " + error.description + ".");
|
|
}
|
|
}
|
|
|
|
return compilererrors;
|
|
}
|
|
|
|
//mxd. Update includes list and script names
|
|
internal List<CompilerError> UpdateScriptNames()
|
|
{
|
|
List<ScriptItem> namedscriptslist = new List<ScriptItem>();
|
|
List<ScriptItem> numberedscriptslist = new List<ScriptItem>();
|
|
List<string> scripincludeslist = new List<string>();
|
|
List<CompilerError> compilererrors = new List<CompilerError>();
|
|
|
|
// Load the script lumps
|
|
foreach(MapLumpInfo maplumpinfo in config.MapLumps.Values)
|
|
{
|
|
// Is this a script lump?
|
|
if((maplumpinfo.ScriptBuild || maplumpinfo.Script != null) && maplumpinfo.Name == "SCRIPTS")
|
|
{
|
|
ScriptConfiguration scriptconfig;
|
|
if(maplumpinfo.ScriptBuild)
|
|
{
|
|
//mxd. More boilderplate
|
|
if(!General.CompiledScriptConfigs.ContainsKey(General.Map.Options.ScriptCompiler))
|
|
{
|
|
compilererrors.Add(new CompilerError("Unable to compile lump '" + maplumpinfo.Name + "'. Unable to find required script compiler configuration ('" + General.Map.Options.ScriptCompiler + "')."));
|
|
return compilererrors;
|
|
}
|
|
|
|
scriptconfig = General.CompiledScriptConfigs[General.Map.Options.ScriptCompiler];
|
|
}
|
|
else
|
|
{
|
|
scriptconfig = maplumpinfo.Script;
|
|
}
|
|
|
|
// Load the lump data
|
|
MemoryStream stream = GetLumpData(maplumpinfo.Name);
|
|
if(stream != null && stream.Length > 0 && scriptconfig != null && scriptconfig.Compiler != null)
|
|
{
|
|
// Get script names
|
|
AcsParserSE parser = new AcsParserSE { OnInclude = (se, path, includetype) => se.Parse(General.Map.Data.LoadFile(path), path, true, includetype, false) };
|
|
|
|
//INFO: CompileLump() prepends lumpname with "?" to distinguish between temporary files and files compiled in place
|
|
if(parser.Parse(stream, "?SCRIPTS", scriptconfig.Compiler.Files, true, AcsParserSE.IncludeType.NONE, false))
|
|
{
|
|
// Add them to arrays
|
|
namedscriptslist.AddRange(parser.NamedScripts);
|
|
numberedscriptslist.AddRange(parser.NumberedScripts);
|
|
scripincludeslist.AddRange(parser.Includes);
|
|
}
|
|
|
|
// Check for errors
|
|
if(parser.HasError)
|
|
{
|
|
compilererrors.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to collections
|
|
scriptincludes.Clear();
|
|
if(compilererrors.Count == 0)
|
|
{
|
|
namedscripts = new Dictionary<string, ScriptItem>(namedscriptslist.Count);
|
|
numberedscripts = new Dictionary<int, ScriptItem>(numberedscriptslist.Count);
|
|
|
|
// Sort script names
|
|
namedscriptslist.Sort(ScriptItem.SortByName);
|
|
numberedscriptslist.Sort(ScriptItem.SortByIndex);
|
|
|
|
foreach(ScriptItem item in namedscriptslist)
|
|
if(!namedscripts.ContainsKey(item.Name.ToLowerInvariant())) namedscripts.Add(item.Name.ToLowerInvariant(), item);
|
|
foreach(ScriptItem item in numberedscriptslist)
|
|
if(!numberedscripts.ContainsKey(item.Index)) numberedscripts.Add(item.Index, item);
|
|
foreach(string include in scripincludeslist)
|
|
if(!scriptincludes.Contains(include)) scriptincludes.Add(include);
|
|
}
|
|
else
|
|
{
|
|
// Clear collections
|
|
namedscripts.Clear();
|
|
numberedscripts.Clear();
|
|
}
|
|
|
|
return compilererrors;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// This updates everything after the configuration or settings have been changed
|
|
internal void UpdateConfiguration()
|
|
{
|
|
// Update map
|
|
map.UpdateConfiguration();
|
|
|
|
// Update settings
|
|
renderer3d.CreateProjection();
|
|
renderer3d.UpdateVertexHandle(); //mxd
|
|
|
|
// Things filters
|
|
General.MainWindow.UpdateThingsFilters();
|
|
}
|
|
|
|
// This changes thing filter
|
|
public void ChangeThingFilter(ThingsFilter newfilter)
|
|
{
|
|
// We have a special filter for null
|
|
if(newfilter == null) newfilter = new NullThingsFilter();
|
|
|
|
// Deactivate old filter
|
|
if(thingsfilter != null) thingsfilter.Deactivate();
|
|
|
|
// Change
|
|
thingsfilter = newfilter;
|
|
|
|
// Activate filter
|
|
thingsfilter.Activate();
|
|
|
|
// Update interface
|
|
General.MainWindow.ReflectThingsFilter();
|
|
|
|
// Redraw
|
|
General.MainWindow.RedrawDisplay();
|
|
}
|
|
|
|
// This sets a new mapset for editing
|
|
private void ChangeMapSet(MapSet newmap)
|
|
{
|
|
// Let the plugin and editing mode know
|
|
General.Plugins.OnMapSetChangeBegin();
|
|
if(General.Editing.Mode != null) General.Editing.Mode.OnMapSetChangeBegin();
|
|
this.visualcamera.Sector = null;
|
|
|
|
// Can't have a selection in an old map set
|
|
map.ClearAllSelected();
|
|
|
|
// Reset surfaces
|
|
renderer2d.Surfaces.Reset();
|
|
|
|
// Apply
|
|
map.Dispose();
|
|
map = newmap;
|
|
map.UpdateConfiguration();
|
|
map.SnapAllToAccuracy();
|
|
map.Update();
|
|
thingsfilter.Update();
|
|
|
|
// Let the plugin and editing mode know
|
|
General.Plugins.OnMapSetChangeEnd();
|
|
if(General.Editing.Mode != null) General.Editing.Mode.OnMapSetChangeEnd();
|
|
}
|
|
|
|
// This reloads resources
|
|
[BeginAction("reloadresources")]
|
|
internal void DoReloadResource()
|
|
{
|
|
//mxd. Get rid of old errors
|
|
General.ErrorLogger.Clear();
|
|
|
|
// Set this to false so we can see if errors are added
|
|
General.ErrorLogger.IsErrorAdded = false;
|
|
|
|
#if DEBUG
|
|
DebugConsole.Clear();
|
|
#endif
|
|
|
|
ReloadResources();
|
|
|
|
if(General.ErrorLogger.IsErrorAdded)
|
|
{
|
|
// Show any errors if preferred
|
|
General.MainWindow.DisplayStatus(StatusType.Warning, "There were errors during resources loading!");
|
|
if(General.Settings.ShowErrorsWindow) General.MainWindow.ShowErrors();
|
|
}
|
|
else
|
|
{
|
|
General.MainWindow.DisplayReady();
|
|
}
|
|
|
|
}
|
|
|
|
internal void ReloadResources()
|
|
{
|
|
// Keep old display info
|
|
StatusInfo oldstatus = General.MainWindow.Status;
|
|
Cursor oldcursor = Cursor.Current;
|
|
|
|
// Show status
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Reloading data resources...");
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
// Clean up
|
|
data.Dispose();
|
|
data = null;
|
|
config = null;
|
|
configinfo = null;
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect(); //mxd
|
|
|
|
// Clear errors
|
|
General.ErrorLogger.Clear();
|
|
|
|
// Reload game configuration
|
|
General.WriteLogLine("Reloading game configuration...");
|
|
configinfo = General.GetConfigurationInfo(options.ConfigFile);
|
|
config = new GameConfiguration(configinfo.Configuration); //mxd
|
|
General.Editing.UpdateCurrentEditModes();
|
|
|
|
// Reload data resources
|
|
General.WriteLogLine("Reloading data resources...");
|
|
data = new DataManager();
|
|
if(!string.IsNullOrEmpty(filepathname))
|
|
{
|
|
DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, false, false, false);
|
|
data.Load(configinfo.Resources, options.Resources, maplocation);
|
|
}
|
|
else
|
|
{
|
|
data.Load(configinfo.Resources, options.Resources);
|
|
}
|
|
|
|
// Apply new settings to map elements
|
|
map.UpdateConfiguration();
|
|
|
|
// Re-link the background image
|
|
grid.LinkBackground();
|
|
|
|
// Inform all plugins that the resources are reloaded
|
|
General.Plugins.ReloadResources();
|
|
|
|
// Inform editing mode that the resources are reloaded
|
|
if(General.Editing.Mode != null)
|
|
{
|
|
General.Editing.Mode.OnReloadResources();
|
|
|
|
//mxd. Also Check appropriate button on interface
|
|
General.MainWindow.CheckEditModeButton(General.Editing.Mode.EditModeButtonName);
|
|
}
|
|
|
|
// Reset status
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
Cursor.Current = oldcursor;
|
|
|
|
//mxd. Update includes list and script names
|
|
UpdateScriptNames(true);
|
|
}
|
|
|
|
// Game Configuration action
|
|
[BeginAction("mapoptions")]
|
|
internal void ShowMapOptions()
|
|
{
|
|
// Cancel volatile mode, if any
|
|
General.Editing.DisengageVolatileMode();
|
|
|
|
// Show map options dialog
|
|
MapOptionsForm optionsform = new MapOptionsForm(options, false);
|
|
if(optionsform.ShowDialog(General.MainWindow) == DialogResult.OK)
|
|
{
|
|
// Update interface
|
|
//General.MainWindow.UpdateInterface();
|
|
|
|
// Stop data manager
|
|
data.Dispose();
|
|
|
|
// Apply new options
|
|
this.options = optionsform.Options;
|
|
|
|
// Load new game configuration
|
|
General.WriteLogLine("Loading game configuration '" + options.ConfigFile + "'...");
|
|
configinfo = General.GetConfigurationInfo(options.ConfigFile);
|
|
Type oldiotype = io.GetType(); //mxd
|
|
|
|
//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, tempwad, this);
|
|
|
|
//mxd. Some lumps may've become unneeded during map format conversion.
|
|
if(oldiotype != io.GetType())
|
|
RemoveUnneededLumps(tempwad, TEMP_MAP_HEADER, false);
|
|
|
|
// Create required lumps if they don't exist yet
|
|
CreateRequiredLumps(tempwad, TEMP_MAP_HEADER);
|
|
|
|
// Let the plugins know
|
|
General.Plugins.MapReconfigure();
|
|
|
|
//mxd. Update linedef color presets and flags if required
|
|
if(oldiotype == typeof(UniversalMapSetIO) && !(io is UniversalMapSetIO))
|
|
{
|
|
foreach(Linedef l in General.Map.Map.Linedefs) l.TranslateFromUDMF();
|
|
foreach(Thing t in General.Map.Map.Things) t.TranslateFromUDMF();
|
|
}
|
|
else if (oldiotype == typeof(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();
|
|
|
|
// Update interface
|
|
General.MainWindow.SetupInterface();
|
|
General.MainWindow.UpdateThingsFilters();
|
|
General.MainWindow.UpdateLinedefColorPresets(); //mxd
|
|
General.MainWindow.UpdateInterface();
|
|
|
|
//mxd. Translate texture names
|
|
bool nameschanged = map.TranslateTextureNames(config.UseLongTextureNames, false);
|
|
grid.TranslateBackgroundName(config.UseLongTextureNames);
|
|
|
|
//mxd. Sector textures may've been changed
|
|
if(nameschanged) data.UpdateUsedTextures();
|
|
|
|
// Done
|
|
General.MainWindow.DisplayReady();
|
|
General.MainWindow.RedrawDisplay(); //mxd
|
|
}
|
|
|
|
// Done
|
|
optionsform.Dispose();
|
|
}
|
|
|
|
// This shows the things filters setup
|
|
[BeginAction("thingsfilterssetup")]
|
|
internal void ShowThingsFiltersSetup()
|
|
{
|
|
new ThingsFiltersForm().ShowDialog(General.MainWindow);
|
|
}
|
|
|
|
//mxd. This shows the linedef color presets window
|
|
[BeginAction("linedefcolorssetup")]
|
|
internal void ShowLinedefColorsSetup()
|
|
{
|
|
// Show things filter dialog
|
|
new LinedefColorPresetsForm().ShowDialog(General.MainWindow);
|
|
}
|
|
|
|
// This returns true is the given type matches
|
|
public bool IsType(Type t)
|
|
{
|
|
return io.GetType() == t;
|
|
}
|
|
|
|
//mxd
|
|
[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();
|
|
}
|
|
|
|
// Let the current editing mode know that we changed something
|
|
General.Editing.Mode.OnMapElementsChanged();
|
|
|
|
// Done
|
|
General.Interface.RedrawDisplay();
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
|
|
//JBR
|
|
[BeginAction("snapvertsto1mp")]
|
|
private void SnapSelectedMapElementsTo1mp()
|
|
{
|
|
// 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("Force snap map elements to 1mp");
|
|
|
|
// Do the snapping
|
|
Cursor.Current = Cursors.AppStarting;
|
|
|
|
// Snap vertices?
|
|
int snappedverts = (vertstosnap.Count > 0 ? SnapVertices(vertstosnap.Values, false, true) : 0);
|
|
|
|
// Snap things?..
|
|
int snappedthings = (things.Count > 0 ? SnapThings(things, false, true) : 0);
|
|
|
|
// Assemble status message
|
|
List<string> message = new List<string>();
|
|
if (snappedverts > 0) message.Add(snappedverts + " vertices");
|
|
if (snappedthings > 0) message.Add(snappedthings + " things");
|
|
|
|
// Display status
|
|
General.Interface.DisplayStatus(StatusType.Info, "Force snapped " + string.Join(" and ", message.ToArray()));
|
|
|
|
// 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;
|
|
|
|
// Let the current editing mode know that we changed something
|
|
General.Editing.Mode.OnMapElementsChanged();
|
|
|
|
// Done
|
|
General.Interface.RedrawDisplay();
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
|
|
//mxd
|
|
private int SnapVertices(IEnumerable<Vertex> verts, bool snapToGrid = true, bool addAll = false) //JBR Added snapToGrid & addAll
|
|
{
|
|
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;
|
|
if (snapToGrid) v.SnapToGrid();
|
|
else v.Move(GridSetup.SnappedToGrid(v.Position, 1f, 1f));
|
|
|
|
if (v.Position.x != pos.x || v.Position.y != pos.y || addAll)
|
|
{
|
|
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, bool snapToGrid = true, bool addAll = false) //JBR Added snapToGrid & addAll
|
|
{
|
|
int snappedCount = 0;
|
|
|
|
//snap them all!
|
|
foreach(Thing t in things)
|
|
{
|
|
Vector2D pos = t.Position;
|
|
if (snapToGrid) t.SnapToGrid();
|
|
else t.Move(GridSetup.SnappedToGrid(t.Position, 1f, 1f));
|
|
if (t.Position.x != pos.x || t.Position.y != pos.y || addAll) snappedCount++;
|
|
}
|
|
|
|
return snappedCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This forces the "changed" variable to "false", which is normally not possible when setting the "IsChanged" property.
|
|
/// USE WITH CARE! This should only be used in very specific circumstances, where the "IsChanged" property is set to true
|
|
/// because of internal behavior
|
|
/// </summary>
|
|
internal void ForceMapIsChangedFalse()
|
|
{
|
|
changed = false;
|
|
General.MainWindow.UpdateMapChangedStatus();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |