mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-12-03 17:12:06 +00:00
3a0426cd7f
MODELDEFs and models can now be loaded from PK3 resources (does anybody know if models or MODELDEFs can be stored in WADs?). Negative scale and zOffset are now correctly parsed from MODELDEFs (model surfaces may be inverted though). Fixed a crash while reading MODELDEFs when Operation System's decimal separator was set to comma. Fixed a crash when user changed Thing type in Visual mode to a new one with model override, which wasn't previously used in a map. Fixed a bug introduced in 1.06 when models weren't rendered in Visual mode when Fullbright mode was on. Non-breaking space is now correctly handled by all Doom Builder's data parsers. ColorPicker plugin: Fixed a crash when user attempted to open ColorPicker window to edit sector properties in Visual mode, using hilighted surface as selection source, without selecting anything before doing so.
1671 lines
52 KiB
C#
1671 lines
52 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;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Diagnostics;
|
|
using CodeImp.DoomBuilder.Windows;
|
|
using CodeImp.DoomBuilder.IO;
|
|
using CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Editing;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.Data;
|
|
using CodeImp.DoomBuilder.Actions;
|
|
using CodeImp.DoomBuilder.Config;
|
|
using CodeImp.DoomBuilder.Plugins;
|
|
using CodeImp.DoomBuilder.Compilers;
|
|
using CodeImp.DoomBuilder.VisualModes;
|
|
|
|
//mxd
|
|
using CodeImp.DoomBuilder.GZBuilder;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder
|
|
{
|
|
public sealed class MapManager
|
|
{
|
|
#region ================== Constants
|
|
|
|
// Map header name in temporary file
|
|
internal const string TEMP_MAP_HEADER = "TEMPMAP";
|
|
internal const string BUILD_MAP_HEADER = "MAP01";
|
|
public const string CONFIG_MAP_HEADER = "~MAP";
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
// Status
|
|
private bool changed;
|
|
private bool scriptschanged;
|
|
|
|
// Map information
|
|
private string filetitle;
|
|
private string filepathname;
|
|
private string temppath;
|
|
|
|
// Main objects
|
|
private MapSet map;
|
|
private MapSetIO io;
|
|
private MapOptions options;
|
|
private ConfigurationInfo configinfo;
|
|
private GameConfiguration config;
|
|
private DataManager data;
|
|
private D3DDevice graphics;
|
|
private Renderer2D renderer2d;
|
|
private Renderer3D renderer3d;
|
|
private WAD tempwad;
|
|
private GridSetup grid;
|
|
private UndoManager undoredo;
|
|
private CopyPasteManager copypaste;
|
|
private Launcher launcher;
|
|
private ThingsFilter thingsfilter;
|
|
private ScriptEditorForm scriptwindow;
|
|
private List<CompilerError> errors;
|
|
private VisualCamera visualcamera;
|
|
|
|
// Disposing
|
|
private bool isdisposed = false;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public string FilePathName { get { return filepathname; } }
|
|
public string FileTitle { get { return filetitle; } }
|
|
public string TempPath { get { return temppath; } }
|
|
public MapOptions Options { get { return options; } }
|
|
public MapSet Map { get { return map; } }
|
|
public DataManager Data { get { return data; } }
|
|
public bool IsChanged { get { return changed | CheckScriptChanged(); } set { changed |= value; } }
|
|
public bool IsDisposed { get { return isdisposed; } }
|
|
internal D3DDevice Graphics { get { return graphics; } }
|
|
public IRenderer2D Renderer2D { get { return renderer2d; } }
|
|
public IRenderer3D Renderer3D { get { return renderer3d; } }
|
|
internal Renderer2D CRenderer2D { get { return renderer2d; } }
|
|
internal Renderer3D CRenderer3D { get { return renderer3d; } }
|
|
public GameConfiguration Config { get { return config; } }
|
|
internal ConfigurationInfo ConfigSettings { get { return configinfo; } }
|
|
public GridSetup Grid { get { return grid; } }
|
|
public UndoManager UndoRedo { get { return undoredo; } }
|
|
internal CopyPasteManager CopyPaste { get { return copypaste; } }
|
|
public IMapSetIO FormatInterface { get { return io; } }
|
|
internal Launcher Launcher { get { return launcher; } }
|
|
public ThingsFilter ThingsFilter { get { return thingsfilter; } }
|
|
internal List<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; } }
|
|
|
|
#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>();
|
|
}
|
|
|
|
// Disposer
|
|
internal bool Dispose()
|
|
{
|
|
// Not already disposed?
|
|
if(!isdisposed)
|
|
{
|
|
// Let the plugins know
|
|
General.Plugins.OnMapCloseBegin();
|
|
|
|
// Stop processing
|
|
General.MainWindow.StopProcessing();
|
|
|
|
// Close script editor
|
|
CloseScriptEditor(false);
|
|
|
|
// Change to no mode
|
|
General.Editing.ChangeMode((EditMode)null);
|
|
|
|
// Unbind any methods
|
|
General.Actions.UnbindMethods(this);
|
|
|
|
// Dispose
|
|
if(grid != null) grid.Dispose();
|
|
if(launcher != null) launcher.Dispose();
|
|
if(copypaste != null) copypaste.Dispose();
|
|
if(undoredo != null) undoredo.Dispose();
|
|
General.WriteLogLine("Unloading data resources...");
|
|
if(data != null) data.Dispose();
|
|
General.WriteLogLine("Closing temporary file...");
|
|
if(tempwad != null) tempwad.Dispose();
|
|
General.WriteLogLine("Unloading map data...");
|
|
if(map != null) map.Dispose();
|
|
General.WriteLogLine("Stopping graphics device...");
|
|
if(renderer2d != null) renderer2d.Dispose();
|
|
if(renderer3d != null) renderer3d.Dispose();
|
|
if(graphics != null) graphics.Dispose();
|
|
visualcamera = null;
|
|
grid = null;
|
|
launcher = null;
|
|
copypaste = null;
|
|
undoredo = null;
|
|
data = null;
|
|
tempwad = null;
|
|
map = null;
|
|
renderer2d = null;
|
|
renderer3d = null;
|
|
graphics = null;
|
|
|
|
// We may spend some time to clean things up here
|
|
GC.Collect();
|
|
|
|
// Remove temp file
|
|
General.WriteLogLine("Removing temporary directory...");
|
|
try { Directory.Delete(temppath, true); } catch(Exception e)
|
|
{
|
|
General.WriteLogLine(e.GetType().Name + ": " + e.Message);
|
|
General.WriteLogLine("Failed to remove temporary directory!");
|
|
}
|
|
|
|
// Let the plugins know
|
|
General.Plugins.OnMapCloseEnd();
|
|
|
|
// Done
|
|
isdisposed = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Already closed
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== New / Open
|
|
|
|
// Initializes for a new map
|
|
internal bool InitializeNewMap(MapOptions options)
|
|
{
|
|
string tempfile;
|
|
|
|
// Apply settings
|
|
this.filetitle = "unnamed.wad";
|
|
this.filepathname = "";
|
|
this.changed = false;
|
|
this.options = options;
|
|
|
|
General.WriteLogLine("Creating new map '" + options.CurrentName + "' with configuration '" + options.ConfigFile + "'");
|
|
|
|
// Initiate graphics
|
|
General.WriteLogLine("Initializing graphics device...");
|
|
graphics = new D3DDevice(General.MainWindow.Display);
|
|
if(!graphics.Initialize()) return false;
|
|
|
|
// Create renderers
|
|
renderer2d = new Renderer2D(graphics);
|
|
renderer3d = new Renderer3D(graphics);
|
|
|
|
// Load game configuration
|
|
General.WriteLogLine("Loading game configuration...");
|
|
configinfo = General.GetConfigurationInfo(options.ConfigFile);
|
|
config = new GameConfiguration(General.LoadGameConfiguration(options.ConfigFile));
|
|
configinfo.ApplyDefaults(config);
|
|
General.Editing.UpdateCurrentEditModes();
|
|
|
|
// Create map data
|
|
map = new MapSet();
|
|
|
|
// Create temp wadfile
|
|
tempfile = General.MakeTempFilename(temppath);
|
|
General.WriteLogLine("Creating temporary file: " + tempfile);
|
|
#if DEBUG
|
|
tempwad = new WAD(tempfile);
|
|
#else
|
|
try { tempwad = new WAD(tempfile); }
|
|
catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Read the map from temp file
|
|
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
|
|
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
|
|
|
|
// Create required lumps
|
|
General.WriteLogLine("Creating map data structures...");
|
|
tempwad.Insert(TEMP_MAP_HEADER, 0, 0);
|
|
io.Write(map, TEMP_MAP_HEADER, 1);
|
|
CreateRequiredLumps(tempwad, TEMP_MAP_HEADER);
|
|
|
|
// Load data manager
|
|
General.WriteLogLine("Loading data resources...");
|
|
data = new DataManager();
|
|
data.Load(configinfo.Resources, options.Resources);
|
|
|
|
// Update structures
|
|
options.ApplyGridSettings();
|
|
map.UpdateConfiguration();
|
|
map.Update();
|
|
thingsfilter.Update();
|
|
|
|
//mxd. load models
|
|
data.LoadModels();
|
|
|
|
// 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);
|
|
|
|
//mxd
|
|
GZBuilder.GZGeneral.OnMapOpenEnd();
|
|
|
|
// Success
|
|
this.changed = false;
|
|
General.WriteLogLine("Map creation done");
|
|
return true;
|
|
}
|
|
|
|
// Initializes for an existing map
|
|
internal bool InitializeOpenMap(string filepathname, MapOptions options)
|
|
{
|
|
WAD mapwad;
|
|
string tempfile;
|
|
DataLocation maplocation;
|
|
|
|
// Apply settings
|
|
this.filetitle = Path.GetFileName(filepathname);
|
|
this.filepathname = filepathname;
|
|
this.changed = false;
|
|
this.options = options;
|
|
|
|
General.WriteLogLine("Opening map '" + options.CurrentName + "' with configuration '" + options.ConfigFile + "'");
|
|
|
|
// Initiate graphics
|
|
General.WriteLogLine("Initializing graphics device...");
|
|
graphics = new D3DDevice(General.MainWindow.Display);
|
|
if(!graphics.Initialize()) return false;
|
|
|
|
// Create renderers
|
|
renderer2d = new Renderer2D(graphics);
|
|
renderer3d = new Renderer3D(graphics);
|
|
|
|
// Load game configuration
|
|
General.WriteLogLine("Loading game configuration...");
|
|
configinfo = General.GetConfigurationInfo(options.ConfigFile);
|
|
config = new GameConfiguration(General.LoadGameConfiguration(options.ConfigFile));
|
|
configinfo.ApplyDefaults(config);
|
|
General.Editing.UpdateCurrentEditModes();
|
|
|
|
// Create map data
|
|
map = new MapSet();
|
|
|
|
// Create temp wadfile
|
|
tempfile = General.MakeTempFilename(temppath);
|
|
General.WriteLogLine("Creating temporary file: " + tempfile);
|
|
#if DEBUG
|
|
tempwad = new WAD(tempfile);
|
|
#else
|
|
try { tempwad = new WAD(tempfile); }
|
|
catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Now open the map file
|
|
General.WriteLogLine("Opening source file: " + filepathname);
|
|
#if DEBUG
|
|
mapwad = new WAD(filepathname, true);
|
|
#else
|
|
try { mapwad = new WAD(filepathname, true); }
|
|
catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while opening source wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Copy the map lumps to the temp file
|
|
General.WriteLogLine("Copying map lumps to temporary file...");
|
|
CopyLumpsByType(mapwad, options.CurrentName, tempwad, TEMP_MAP_HEADER,
|
|
true, true, true, true);
|
|
|
|
// Close the map file
|
|
mapwad.Dispose();
|
|
|
|
// Read the map from temp file
|
|
map.BeginAddRemove();
|
|
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
|
|
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
|
|
General.WriteLogLine("Reading map data structures from file...");
|
|
#if DEBUG
|
|
map = io.Read(map, TEMP_MAP_HEADER);
|
|
#else
|
|
try { map = io.Read(map, TEMP_MAP_HEADER); }
|
|
catch(Exception e)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Unable to read the map data structures with the specified configuration. " + e.GetType().Name + ": " + e.Message);
|
|
General.ShowErrorMessage("Unable to read the map data structures with the specified configuration.", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
map.EndAddRemove();
|
|
|
|
// Load data manager
|
|
General.WriteLogLine("Loading data resources...");
|
|
data = new DataManager();
|
|
maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false);
|
|
data.Load(configinfo.Resources, options.Resources, maplocation);
|
|
|
|
// Remove unused sectors
|
|
map.RemoveUnusedSectors(true);
|
|
|
|
// Update structures
|
|
options.ApplyGridSettings();
|
|
map.UpdateConfiguration();
|
|
map.SnapAllToAccuracy();
|
|
map.Update();
|
|
thingsfilter.Update();
|
|
|
|
//mxd. load models
|
|
data.LoadModels();
|
|
|
|
// 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();
|
|
|
|
//mxd
|
|
GZBuilder.GZGeneral.OnMapOpenEnd();
|
|
|
|
// Success
|
|
this.changed = false;
|
|
General.WriteLogLine("Map loading done");
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Save
|
|
|
|
// Initializes for an existing map
|
|
internal bool SaveMap(string newfilepathname, SavePurpose purpose)
|
|
{
|
|
MapSet outputset;
|
|
string nodebuildername, settingsfile;
|
|
StatusInfo oldstatus;
|
|
WAD targetwad;
|
|
int index;
|
|
bool includenodes = false;
|
|
string origmapname;
|
|
bool success = true;
|
|
|
|
General.WriteLogLine("Saving map to file: " + newfilepathname);
|
|
|
|
// Scripts changed?
|
|
bool localscriptschanged = CheckScriptChanged();
|
|
|
|
// If the scripts window is open, save the scripts first
|
|
if(IsScriptsWindowOpen) scriptwindow.Editor.ImplicitSave();
|
|
|
|
// Only recompile scripts when the scripts have changed
|
|
// (not when only the map changed)
|
|
if(localscriptschanged)
|
|
{
|
|
if(!CompileScriptLumps())
|
|
{
|
|
// Compiler failure
|
|
if(errors.Count > 0)
|
|
General.ShowErrorMessage("Error while compiling scripts: " + errors[0].description, MessageBoxButtons.OK);
|
|
else
|
|
General.ShowErrorMessage("Unknown compiler error while compiling scripts!", MessageBoxButtons.OK);
|
|
}
|
|
}
|
|
|
|
// Show script window if there are any errors and we are going to test the map
|
|
// and always update the errors on the scripts window.
|
|
if((errors.Count > 0) && (scriptwindow == null) && (purpose == SavePurpose.Testing)) ShowScriptEditor();
|
|
if(scriptwindow != null) scriptwindow.Editor.ShowErrors(errors);
|
|
|
|
// Only write the map and rebuild nodes when the actual map has changed
|
|
// (not when only scripts have changed)
|
|
if(changed)
|
|
{
|
|
// Make a copy of the map data
|
|
outputset = map.Clone();
|
|
|
|
// Remove all flags from all 3D Start things
|
|
foreach(Thing t in outputset.Things)
|
|
{
|
|
if(t.Type == config.Start3DModeThingType)
|
|
{
|
|
// We're not using SetFlag here, this doesn't have to be undone.
|
|
// Please note that this is totally exceptional!
|
|
List<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
|
|
oldstatus = General.MainWindow.Status;
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Compressing sidedefs...");
|
|
outputset.CompressSidedefs();
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
|
|
// Check if it still doesnt fit
|
|
if(outputset.Sidedefs.Count > io.MaxSidedefs)
|
|
{
|
|
// Problem! Can't save the map like this!
|
|
General.ShowErrorMessage("Unable to save the map: There are too many unique sidedefs!", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check things
|
|
if(map.Things.Count > io.MaxThings)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many things!", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
// Check sectors
|
|
if(map.Sectors.Count > io.MaxSectors)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many sectors!", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
// Check linedefs
|
|
if(map.Linedefs.Count > io.MaxLinedefs)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many linedefs!", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
// Check vertices
|
|
if(map.Vertices.Count > io.MaxVertices)
|
|
{
|
|
General.ShowErrorMessage("Unable to save the map: There are too many vertices!", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
// TODO: Check for more limitations
|
|
|
|
// Write to temporary file
|
|
General.WriteLogLine("Writing map data structures to file...");
|
|
index = tempwad.FindLumpIndex(TEMP_MAP_HEADER);
|
|
if(index == -1) index = 0;
|
|
io.Write(outputset, TEMP_MAP_HEADER, index);
|
|
outputset.Dispose();
|
|
|
|
// Get the corresponding nodebuilder
|
|
nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave;
|
|
|
|
// Build the nodes
|
|
oldstatus = General.MainWindow.Status;
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes...");
|
|
if(!string.IsNullOrEmpty(nodebuildername))
|
|
includenodes = BuildNodes(nodebuildername, true);
|
|
else
|
|
includenodes = false;
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
}
|
|
else
|
|
{
|
|
// Check if we have nodebuilder lumps
|
|
includenodes = VerifyNodebuilderLumps(tempwad, TEMP_MAP_HEADER);
|
|
}
|
|
|
|
// Suspend data resources
|
|
data.Suspend();
|
|
|
|
// Determine original map name
|
|
origmapname = (options.PreviousName != "") ? options.PreviousName : options.CurrentName;
|
|
|
|
try
|
|
{
|
|
// Backup existing file, if any
|
|
if(File.Exists(newfilepathname))
|
|
{
|
|
if(File.Exists(newfilepathname + ".backup3")) File.Delete(newfilepathname + ".backup3");
|
|
if(File.Exists(newfilepathname + ".backup2")) File.Move(newfilepathname + ".backup2", newfilepathname + ".backup3");
|
|
if(File.Exists(newfilepathname + ".backup1")) File.Move(newfilepathname + ".backup1", newfilepathname + ".backup2");
|
|
File.Copy(newfilepathname, newfilepathname + ".backup1");
|
|
}
|
|
|
|
// Except when saving INTO another file,
|
|
// kill the target file if it is different from source file
|
|
if((purpose != SavePurpose.IntoFile) && (newfilepathname != filepathname))
|
|
{
|
|
// Kill target file
|
|
if(File.Exists(newfilepathname)) File.Delete(newfilepathname);
|
|
|
|
// Kill .dbs settings file
|
|
settingsfile = newfilepathname.Substring(0, newfilepathname.Length - 4) + ".dbs";
|
|
if(File.Exists(settingsfile)) File.Delete(settingsfile);
|
|
}
|
|
|
|
// On Save AS we have to copy the previous file to the new file
|
|
if((purpose == SavePurpose.AsNewFile) && (filepathname != ""))
|
|
{
|
|
// Copy if original file still exists
|
|
if(File.Exists(filepathname)) File.Copy(filepathname, newfilepathname, true);
|
|
}
|
|
|
|
// If the target file exists, we need to rebuild it
|
|
if(File.Exists(newfilepathname))
|
|
{
|
|
// Move the target file aside
|
|
string origwadfile = newfilepathname + ".temp";
|
|
File.Move(newfilepathname, origwadfile);
|
|
|
|
// Open original file
|
|
WAD origwad = new WAD(origwadfile, true);
|
|
|
|
// Create new target file
|
|
targetwad = new WAD(newfilepathname);
|
|
|
|
// Copy all lumps, except the original map
|
|
CopyAllLumpsExceptMap(origwad, targetwad, origmapname);
|
|
|
|
// Close original file and delete it
|
|
origwad.Dispose();
|
|
File.Delete(origwadfile);
|
|
}
|
|
else
|
|
{
|
|
// Create new target file
|
|
targetwad = new WAD(newfilepathname);
|
|
}
|
|
}
|
|
catch(IOException)
|
|
{
|
|
General.ShowErrorMessage("IO Error while writing target file: " + newfilepathname + ". Please make sure the location is accessible and not in use by another program.", MessageBoxButtons.OK);
|
|
data.Resume();
|
|
General.WriteLogLine("Map saving failed");
|
|
return false;
|
|
}
|
|
catch(UnauthorizedAccessException)
|
|
{
|
|
General.ShowErrorMessage("Error while accessing target file: " + newfilepathname + ". Please make sure the location is accessible and not in use by another program.", MessageBoxButtons.OK);
|
|
data.Resume();
|
|
General.WriteLogLine("Map saving failed");
|
|
return false;
|
|
}
|
|
|
|
// Copy map lumps to target file
|
|
CopyLumpsByType(tempwad, TEMP_MAP_HEADER, targetwad, origmapname, true, true, includenodes, true);
|
|
|
|
// Was the map lump name renamed?
|
|
if((options.PreviousName != options.CurrentName) &&
|
|
(options.PreviousName != ""))
|
|
{
|
|
General.WriteLogLine("Renaming map lump name from " + options.PreviousName + " to " + options.CurrentName);
|
|
|
|
// Find the map header in target
|
|
index = targetwad.FindLumpIndex(options.PreviousName);
|
|
if(index > -1)
|
|
{
|
|
// Rename the map lump name
|
|
targetwad.Lumps[index].Rename(options.CurrentName);
|
|
options.PreviousName = "";
|
|
}
|
|
else
|
|
{
|
|
// Houston, we've got a problem!
|
|
General.ShowErrorMessage("Error renaming map lump name: the original map lump could not be found!", MessageBoxButtons.OK);
|
|
options.CurrentName = options.PreviousName;
|
|
options.PreviousName = "";
|
|
}
|
|
}
|
|
|
|
// Done with the target file
|
|
targetwad.Dispose();
|
|
|
|
// Resume data resources
|
|
data.Resume();
|
|
|
|
// Not saved for testing purpose?
|
|
if(purpose != SavePurpose.Testing)
|
|
{
|
|
// Saved in a different file?
|
|
if(newfilepathname != filepathname)
|
|
{
|
|
// Keep new filename
|
|
filepathname = newfilepathname;
|
|
filetitle = Path.GetFileName(filepathname);
|
|
|
|
// Reload resources
|
|
ReloadResources();
|
|
}
|
|
|
|
try
|
|
{
|
|
// Open or create the map settings
|
|
settingsfile = newfilepathname.Substring(0, newfilepathname.Length - 4) + ".dbs";
|
|
options.WriteConfiguration(settingsfile);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Warning only
|
|
General.ErrorLogger.Add(ErrorType.Warning, "Could not write the map settings configuration file. " + e.GetType().Name + ": " + e.Message);
|
|
}
|
|
|
|
// Changes saved
|
|
changed = false;
|
|
scriptschanged = false;
|
|
}
|
|
|
|
// Success!
|
|
General.WriteLogLine("Map saving done");
|
|
return success;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Nodebuild
|
|
|
|
// This builds the nodes in the temproary file with the given configuration name
|
|
private bool BuildNodes(string nodebuildername, bool failaswarning)
|
|
{
|
|
NodebuilderInfo nodebuilder;
|
|
string tempfile1, tempfile2, sourcefile;
|
|
bool lumpscomplete = false;
|
|
WAD buildwad;
|
|
|
|
// Find the nodebuilder
|
|
nodebuilder = General.GetNodebuilderByName(nodebuildername);
|
|
if(nodebuilder == null)
|
|
{
|
|
// Problem! Can't find that nodebuilder!
|
|
General.ShowWarningMessage("Unable to build the nodes: The configured nodebuilder cannot be found.\nPlease check your game configuration settings!", MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Create the compiler interface that will run the nodebuilder
|
|
// This automatically creates a temporary directory for us
|
|
Compiler compiler = nodebuilder.CreateCompiler();
|
|
|
|
// Make temporary filename
|
|
tempfile1 = General.MakeTempFilename(compiler.Location);
|
|
|
|
// Make the temporary WAD file
|
|
General.WriteLogLine("Creating temporary build file: " + tempfile1);
|
|
#if DEBUG
|
|
buildwad = new WAD(tempfile1);
|
|
#else
|
|
try { buildwad = new WAD(tempfile1); }
|
|
catch(Exception e)
|
|
{
|
|
General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Determine source file
|
|
if(filepathname.Length > 0)
|
|
sourcefile = filepathname;
|
|
else
|
|
sourcefile = tempwad.Filename;
|
|
|
|
// Copy lumps to buildwad
|
|
General.WriteLogLine("Copying map lumps to temporary build file...");
|
|
CopyLumpsByType(tempwad, TEMP_MAP_HEADER, buildwad, BUILD_MAP_HEADER, true, false, false, true);
|
|
|
|
// Close buildwad
|
|
buildwad.Dispose();
|
|
|
|
// Does the nodebuilder require an output file?
|
|
if(nodebuilder.HasSpecialOutputFile)
|
|
{
|
|
// Make a temporary output file for the nodebuilder
|
|
tempfile2 = General.MakeTempFilename(compiler.Location);
|
|
General.WriteLogLine("Temporary output file: " + tempfile2);
|
|
}
|
|
else
|
|
{
|
|
// Output file is same as input file
|
|
tempfile2 = tempfile1;
|
|
}
|
|
|
|
// Run the nodebuilder
|
|
compiler.Parameters = nodebuilder.Parameters;
|
|
compiler.InputFile = Path.GetFileName(tempfile1);
|
|
compiler.OutputFile = Path.GetFileName(tempfile2);
|
|
compiler.SourceFile = sourcefile;
|
|
compiler.WorkingDirectory = Path.GetDirectoryName(tempfile1);
|
|
if(compiler.Run())
|
|
{
|
|
// Open the output file
|
|
try { buildwad = new WAD(tempfile2); }
|
|
catch(Exception e)
|
|
{
|
|
General.WriteLogLine(e.GetType().Name + " while reading build wad file: " + e.Message);
|
|
buildwad = null;
|
|
}
|
|
|
|
if(buildwad != null)
|
|
{
|
|
// Output lumps complete?
|
|
lumpscomplete = VerifyNodebuilderLumps(buildwad, BUILD_MAP_HEADER);
|
|
}
|
|
|
|
if(lumpscomplete)
|
|
{
|
|
// Copy nodebuilder lumps to temp file
|
|
General.WriteLogLine("Copying nodebuilder lumps to temporary file...");
|
|
CopyLumpsByType(buildwad, BUILD_MAP_HEADER, tempwad, TEMP_MAP_HEADER, false, false, true, false);
|
|
}
|
|
else
|
|
{
|
|
// Nodebuilder did not build the lumps!
|
|
if(failaswarning)
|
|
General.ShowWarningMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures.\nThe map will be saved without the nodes.", MessageBoxButtons.OK);
|
|
else
|
|
General.ShowErrorMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures.", MessageBoxButtons.OK);
|
|
}
|
|
|
|
// Done with the build wad
|
|
if(buildwad != null) buildwad.Dispose();
|
|
}
|
|
|
|
// Clean up
|
|
compiler.Dispose();
|
|
|
|
// Return result
|
|
return lumpscomplete;
|
|
}
|
|
}
|
|
|
|
// This verifies if the nodebuilder lumps exist in a WAD file
|
|
private bool VerifyNodebuilderLumps(WAD wad, string mapheader)
|
|
{
|
|
bool lumpscomplete = false;
|
|
|
|
// Find the map header in source
|
|
int srcindex = wad.FindLumpIndex(mapheader);
|
|
if(srcindex > -1)
|
|
{
|
|
// Go for all the map lump names
|
|
lumpscomplete = true;
|
|
foreach(DictionaryEntry ml in config.MapLumpNames)
|
|
{
|
|
// Read lump settings from map config
|
|
bool lumpnodebuild = config.ReadSetting("maplumpnames." + ml.Key + ".nodebuild", false);
|
|
bool lumpallowempty = config.ReadSetting("maplumpnames." + ml.Key + ".allowempty", false);
|
|
|
|
// Check if this lump should exist
|
|
if(lumpnodebuild && !lumpallowempty)
|
|
{
|
|
// Find the lump in the source
|
|
if(wad.FindLump(ml.Key.ToString(), srcindex, srcindex + config.MapLumpNames.Count + 2) == null)
|
|
{
|
|
// Missing a lump!
|
|
lumpscomplete = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return lumpscomplete;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Lumps
|
|
|
|
// This returns a copy of the requested lump stream data
|
|
// This is copied from the temp wad file and returns null when the lump is not found
|
|
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());
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// This writes a copy of the data to a lump in the temp file
|
|
public void SetLumpData(string lumpname, MemoryStream data)
|
|
{
|
|
int insertindex = tempwad.Lumps.Count;
|
|
|
|
// Remove the lump if it already exists
|
|
int li = tempwad.FindLumpIndex(lumpname);
|
|
if(li > -1)
|
|
{
|
|
insertindex = li;
|
|
tempwad.RemoveAt(li);
|
|
}
|
|
|
|
// Insert new lump
|
|
Lump l = tempwad.Insert(lumpname, insertindex, (int)data.Length);
|
|
l.Stream.Seek(0, SeekOrigin.Begin);
|
|
data.WriteTo(l.Stream);
|
|
}
|
|
|
|
// This checks if the specified lump exists in the temp file
|
|
public bool LumpExists(string lumpname)
|
|
{
|
|
return (tempwad.FindLumpIndex(lumpname) > -1);
|
|
}
|
|
|
|
// This creates empty lumps for those required
|
|
private void CreateRequiredLumps(WAD target, string mapname)
|
|
{
|
|
int headerindex, insertindex, targetindex;
|
|
string lumpname;
|
|
bool lumprequired;
|
|
|
|
// Find the map header in target
|
|
headerindex = target.FindLumpIndex(mapname);
|
|
if(headerindex == -1)
|
|
{
|
|
// If this header doesnt exists in the target
|
|
// then insert at the end of the target
|
|
headerindex = target.Lumps.Count;
|
|
}
|
|
|
|
// Begin inserting at target header index
|
|
insertindex = headerindex;
|
|
|
|
// Go for all the map lump names
|
|
foreach(DictionaryEntry ml in config.MapLumpNames)
|
|
{
|
|
// Read lump settings from map config
|
|
lumprequired = config.ReadSetting("maplumpnames." + ml.Key + ".required", false);
|
|
|
|
// Check if this lump is required
|
|
if(lumprequired)
|
|
{
|
|
// Get the lump name
|
|
lumpname = ml.Key.ToString();
|
|
if(lumpname == CONFIG_MAP_HEADER) lumpname = mapname;
|
|
|
|
// Check if the lump is missing at the target
|
|
targetindex = FindSpecificLump(target, lumpname, headerindex, mapname, config.MapLumpNames);
|
|
if(targetindex == -1)
|
|
{
|
|
// Determine target index
|
|
insertindex++;
|
|
if(insertindex > target.Lumps.Count) insertindex = target.Lumps.Count;
|
|
|
|
// Create new, emtpy lump
|
|
General.WriteLogLine(lumpname + " is required! Created empty lump.");
|
|
target.Insert(lumpname, insertindex, 0);
|
|
}
|
|
else
|
|
{
|
|
// Move insert index
|
|
insertindex = targetindex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This copies all lumps, except those of a specific map
|
|
private void CopyAllLumpsExceptMap(WAD source, WAD target, string sourcemapname)
|
|
{
|
|
// Go for all lumps
|
|
bool skipping = false;
|
|
foreach(Lump srclump in source.Lumps)
|
|
{
|
|
// Check if we should stop skipping lumps here
|
|
if(skipping && !config.MapLumpNames.Contains(srclump.Name))
|
|
{
|
|
// Stop skipping
|
|
skipping = false;
|
|
}
|
|
|
|
// Check if we should start skipping lumps here
|
|
if(!skipping && (srclump.Name == sourcemapname))
|
|
{
|
|
// We have encountered the map header, start skipping!
|
|
skipping = true;
|
|
}
|
|
|
|
// Not skipping this lump?
|
|
if(!skipping)
|
|
{
|
|
// Copy lump over!
|
|
Lump tgtlump = target.Insert(srclump.Name, target.Lumps.Count, srclump.Length);
|
|
srclump.CopyTo(tgtlump);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This copies specific map lumps from one WAD to another
|
|
private void CopyLumpsByType(WAD source, string sourcemapname,
|
|
WAD target, string targetmapname,
|
|
bool copyrequired, bool copyblindcopy,
|
|
bool copynodebuild, bool copyscript)
|
|
{
|
|
bool lumprequired, lumpblindcopy, lumpnodebuild;
|
|
string lumpscript, srclumpname, tgtlumpname;
|
|
int srcheaderindex, tgtheaderindex, targetindex, sourceindex, lumpindex;
|
|
Lump lump, newlump;
|
|
|
|
// Find the map header in target
|
|
tgtheaderindex = target.FindLumpIndex(targetmapname);
|
|
if(tgtheaderindex == -1)
|
|
{
|
|
// If this header doesnt exists in the target
|
|
// then insert at the end of the target
|
|
tgtheaderindex = target.Lumps.Count;
|
|
}
|
|
|
|
// Begin inserting at target header index
|
|
targetindex = tgtheaderindex;
|
|
|
|
// Find the map header in source
|
|
srcheaderindex = source.FindLumpIndex(sourcemapname);
|
|
if(srcheaderindex > -1)
|
|
{
|
|
// Copy the map header from source to target
|
|
//newlump = target.Insert(targetmapname, tgtindex++, source.Lumps[srcindex].Length);
|
|
//source.Lumps[srcindex].CopyTo(newlump);
|
|
|
|
// Go for all the map lump names
|
|
foreach(DictionaryEntry ml in config.MapLumpNames)
|
|
{
|
|
// Read lump settings from map config
|
|
lumprequired = config.ReadSetting("maplumpnames." + ml.Key + ".required", false);
|
|
lumpblindcopy = config.ReadSetting("maplumpnames." + ml.Key + ".blindcopy", false);
|
|
lumpnodebuild = config.ReadSetting("maplumpnames." + ml.Key + ".nodebuild", false);
|
|
lumpscript = config.ReadSetting("maplumpnames." + ml.Key + ".script", "");
|
|
|
|
// Check if this lump should be copied
|
|
if((lumprequired && copyrequired) || (lumpblindcopy && copyblindcopy) ||
|
|
(lumpnodebuild && copynodebuild) || ((lumpscript.Length != 0) && copyscript))
|
|
{
|
|
// Get the lump name
|
|
srclumpname = ml.Key.ToString();
|
|
tgtlumpname = ml.Key.ToString();
|
|
if(srclumpname == CONFIG_MAP_HEADER) srclumpname = sourcemapname;
|
|
if(tgtlumpname == CONFIG_MAP_HEADER) tgtlumpname = targetmapname;
|
|
|
|
// Find the lump in the source
|
|
sourceindex = FindSpecificLump(source, srclumpname, srcheaderindex, sourcemapname, config.MapLumpNames);
|
|
if(sourceindex > -1)
|
|
{
|
|
// Remove lump at target
|
|
lumpindex = RemoveSpecificLump(target, tgtlumpname, tgtheaderindex, targetmapname, config.MapLumpNames);
|
|
|
|
// Determine target index
|
|
// When original lump was found and removed then insert at that position
|
|
// otherwise insert after last insertion position
|
|
if(lumpindex > -1) targetindex = lumpindex; else targetindex++;
|
|
if(targetindex > target.Lumps.Count) targetindex = target.Lumps.Count;
|
|
|
|
// Copy the lump to the target
|
|
//General.WriteLogLine(srclumpname + " copying as " + tgtlumpname);
|
|
lump = source.Lumps[sourceindex];
|
|
newlump = target.Insert(tgtlumpname, targetindex, lump.Length);
|
|
lump.CopyTo(newlump);
|
|
}
|
|
else
|
|
{
|
|
// We don't want to bother the user with this. There are a lot of lumps in
|
|
// the game configs that are trivial and don't need to be found.
|
|
if(lumprequired)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Warning, ml.Key.ToString() + " (required lump) should be read but was not found in the WAD file.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This finds a lump within the range of known lump names
|
|
// Returns -1 when the lump cannot be found
|
|
internal static int FindSpecificLump(WAD source, string lumpname, int mapheaderindex, string mapheadername, IDictionary maplumps)
|
|
{
|
|
// Use the configured map lump names to find the specific lump within range,
|
|
// because when an unknown lump is met, this search must stop.
|
|
|
|
// Go for all lumps in order to find the specified lump
|
|
for(int i = 0; i < maplumps.Count + 1; i++)
|
|
{
|
|
// Still within bounds?
|
|
if((mapheaderindex + i) < source.Lumps.Count)
|
|
{
|
|
// Check if this is a known lump name
|
|
if(maplumps.Contains(source.Lumps[mapheaderindex + i].Name) ||
|
|
(maplumps.Contains(CONFIG_MAP_HEADER) && (source.Lumps[mapheaderindex + i].Name == mapheadername)))
|
|
{
|
|
// Is this the lump we are looking for?
|
|
if(source.Lumps[mapheaderindex + i].Name == lumpname)
|
|
{
|
|
// Return this index
|
|
return mapheaderindex + i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Unknown lump hit, abort search
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing found
|
|
return -1;
|
|
}
|
|
|
|
// This removes a specific lump and returns the position where the lump was removed
|
|
// Returns -1 when the lump could not be found
|
|
internal static int RemoveSpecificLump(WAD source, string lumpname, int mapheaderindex, string mapheadername, IDictionary maplumps)
|
|
{
|
|
int lumpindex;
|
|
|
|
// Find the specific lump index
|
|
lumpindex = FindSpecificLump(source, lumpname, mapheaderindex, mapheadername, maplumps);
|
|
if(lumpindex > -1)
|
|
{
|
|
// Remove this lump
|
|
//General.WriteLogLine(lumpname + " removed");
|
|
source.RemoveAt(lumpindex);
|
|
}
|
|
else
|
|
{
|
|
// Lump not found
|
|
//General.ErrorLogger.Add(ErrorType.Warning, lumpname + " should be removed but was not found!");
|
|
}
|
|
|
|
// Return result
|
|
return lumpindex;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Selection Groups
|
|
|
|
// This adds selection to a group
|
|
private void AddSelectionToGroup(int groupindex)
|
|
{
|
|
General.Interface.SetCursor(Cursors.WaitCursor);
|
|
|
|
// Make undo
|
|
undoredo.CreateUndo("Assign to group " + groupindex);
|
|
|
|
// Make selection
|
|
map.AddSelectionToGroup(0x01 << groupindex);
|
|
|
|
General.Interface.DisplayStatus(StatusType.Action, "Assigned selection to group " + groupindex);
|
|
General.Interface.SetCursor(Cursors.Default);
|
|
}
|
|
|
|
// This selects a group
|
|
private void SelectGroup(int groupindex)
|
|
{
|
|
// Select
|
|
int groupmask = 0x01 << groupindex;
|
|
map.SelectVerticesByGroup(groupmask);
|
|
map.SelectLinedefsByGroup(groupmask);
|
|
map.SelectSectorsByGroup(groupmask);
|
|
map.SelectThingsByGroup(groupmask);
|
|
|
|
// Redraw to show selection
|
|
General.Interface.DisplayStatus(StatusType.Action, "Selected group " + groupindex);
|
|
General.Interface.RedrawDisplay();
|
|
}
|
|
|
|
// Select actions
|
|
[BeginAction("selectgroup1")] internal void SelectGroup1() { SelectGroup(0); }
|
|
[BeginAction("selectgroup2")] internal void SelectGroup2() { SelectGroup(1); }
|
|
[BeginAction("selectgroup3")] internal void SelectGroup3() { SelectGroup(2); }
|
|
[BeginAction("selectgroup4")] internal void SelectGroup4() { SelectGroup(3); }
|
|
[BeginAction("selectgroup5")] internal void SelectGroup5() { SelectGroup(4); }
|
|
[BeginAction("selectgroup6")] internal void SelectGroup6() { SelectGroup(5); }
|
|
[BeginAction("selectgroup7")] internal void SelectGroup7() { SelectGroup(6); }
|
|
[BeginAction("selectgroup8")] internal void SelectGroup8() { SelectGroup(7); }
|
|
[BeginAction("selectgroup9")] internal void SelectGroup9() { SelectGroup(8); }
|
|
[BeginAction("selectgroup10")] internal void SelectGroup10() { SelectGroup(9); }
|
|
|
|
// Assign actions
|
|
[BeginAction("assigngroup1")] internal void AssignGroup1() { AddSelectionToGroup(0); }
|
|
[BeginAction("assigngroup2")] internal void AssignGroup2() { AddSelectionToGroup(1); }
|
|
[BeginAction("assigngroup3")] internal void AssignGroup3() { AddSelectionToGroup(2); }
|
|
[BeginAction("assigngroup4")] internal void AssignGroup4() { AddSelectionToGroup(3); }
|
|
[BeginAction("assigngroup5")] internal void AssignGroup5() { AddSelectionToGroup(4); }
|
|
[BeginAction("assigngroup6")] internal void AssignGroup6() { AddSelectionToGroup(5); }
|
|
[BeginAction("assigngroup7")] internal void AssignGroup7() { AddSelectionToGroup(6); }
|
|
[BeginAction("assigngroup8")] internal void AssignGroup8() { AddSelectionToGroup(7); }
|
|
[BeginAction("assigngroup9")] internal void AssignGroup9() { AddSelectionToGroup(8); }
|
|
[BeginAction("assigngroup10")] internal void AssignGroup10() { AddSelectionToGroup(9); }
|
|
|
|
#endregion
|
|
|
|
#region ================== Script Editing
|
|
|
|
// Show the script editor
|
|
[BeginAction("openscripteditor")]
|
|
internal void ShowScriptEditor()
|
|
{
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
if(scriptwindow == null)
|
|
{
|
|
// Load the window
|
|
scriptwindow = new ScriptEditorForm();
|
|
}
|
|
|
|
// Window not yet visible?
|
|
if(!scriptwindow.Visible)
|
|
{
|
|
// Show the window
|
|
if(General.Settings.ScriptOnTop)
|
|
{
|
|
if(scriptwindow.Visible && (scriptwindow.Owner == null)) scriptwindow.Hide();
|
|
scriptwindow.Show(General.MainWindow);
|
|
}
|
|
else
|
|
{
|
|
if(scriptwindow.Visible && (scriptwindow.Owner != null)) scriptwindow.Hide();
|
|
scriptwindow.Show();
|
|
}
|
|
}
|
|
scriptwindow.Activate();
|
|
scriptwindow.Focus();
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
|
|
// This asks the user to save changes in script files
|
|
// Returns false when cancelled by the user
|
|
internal bool AskSaveScriptChanges()
|
|
{
|
|
// Window open?
|
|
if(scriptwindow != null)
|
|
{
|
|
// Ask to save changes
|
|
// This also saves implicitly
|
|
return scriptwindow.AskSaveAll();
|
|
}
|
|
else
|
|
{
|
|
// No problems
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// This applies the changed status for internal scripts
|
|
internal void ApplyScriptChanged()
|
|
{
|
|
// Remember if lumps are changed
|
|
scriptschanged |= scriptwindow.Editor.CheckImplicitChanges();
|
|
}
|
|
|
|
// Close the script editor
|
|
// Specify true for the closing parameter when
|
|
// the window is already in the closing process
|
|
internal void CloseScriptEditor(bool closing)
|
|
{
|
|
if(scriptwindow != null)
|
|
{
|
|
if(!scriptwindow.IsDisposed)
|
|
{
|
|
// Remember what files were open
|
|
scriptwindow.Editor.WriteOpenFilesToConfiguration();
|
|
|
|
// Close now
|
|
if(!closing) scriptwindow.Close();
|
|
}
|
|
|
|
// Done
|
|
scriptwindow = null;
|
|
}
|
|
}
|
|
|
|
// This checks if the scripts are changed
|
|
internal bool CheckScriptChanged()
|
|
{
|
|
if(scriptwindow != null)
|
|
{
|
|
// Check if scripts are changed
|
|
return scriptschanged || scriptwindow.Editor.CheckImplicitChanges();
|
|
}
|
|
else
|
|
{
|
|
// Check if scripts are changed
|
|
return scriptschanged;
|
|
}
|
|
}
|
|
|
|
// This compiles all lumps that require compiling and stores the results
|
|
// Returns true when our code worked properly (even when the compiler returned errors)
|
|
private bool CompileScriptLumps()
|
|
{
|
|
bool success = true;
|
|
errors.Clear();
|
|
|
|
// Go for all the map lumps
|
|
foreach(MapLumpInfo lumpinfo in config.MapLumps.Values)
|
|
{
|
|
// Is this a script lump?
|
|
if(lumpinfo.script != null)
|
|
{
|
|
// Compile it now
|
|
success &= CompileLump(lumpinfo.name, false);
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// This compiles a script lump and returns any errors that may have occurred
|
|
// Returns true when our code worked properly (even when the compiler returned errors)
|
|
internal bool CompileLump(string lumpname, bool clearerrors)
|
|
{
|
|
string inputfile, outputfile, sourcefile;
|
|
Compiler compiler;
|
|
byte[] filedata;
|
|
string reallumpname = lumpname;
|
|
|
|
// Find the lump
|
|
if(lumpname == CONFIG_MAP_HEADER) reallumpname = TEMP_MAP_HEADER;
|
|
Lump lump = tempwad.FindLump(reallumpname);
|
|
if(lump == null) throw new Exception("No such lump in temporary wad file '" + reallumpname + "'.");
|
|
|
|
// Determine source file
|
|
if(filepathname.Length > 0)
|
|
sourcefile = filepathname;
|
|
else
|
|
sourcefile = tempwad.Filename;
|
|
|
|
// New list of errors
|
|
if(clearerrors) errors.Clear();
|
|
|
|
// Determine the script configuration to use
|
|
ScriptConfiguration scriptconfig = config.MapLumps[lumpname].script;
|
|
if(scriptconfig.Compiler != null)
|
|
{
|
|
try
|
|
{
|
|
// Initialize compiler
|
|
compiler = scriptconfig.Compiler.Create();
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Fail
|
|
errors.Add(new CompilerError("Unable to initialize compiler. " + e.GetType().Name + ": " + e.Message));
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Write lump data to temp script file in compiler's temp directory
|
|
inputfile = General.MakeTempFilename(compiler.Location, "tmp");
|
|
lump.Stream.Seek(0, SeekOrigin.Begin);
|
|
BinaryReader reader = new BinaryReader(lump.Stream);
|
|
File.WriteAllBytes(inputfile, reader.ReadBytes((int)lump.Stream.Length));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Fail
|
|
compiler.Dispose();
|
|
errors.Add(new CompilerError("Unable to write script to working file. " + e.GetType().Name + ": " + e.Message));
|
|
return false;
|
|
}
|
|
|
|
// Make random output filename
|
|
outputfile = General.MakeTempFilename(compiler.Location, "tmp");
|
|
|
|
// Run compiler
|
|
compiler.Parameters = scriptconfig.Parameters;
|
|
compiler.InputFile = Path.GetFileName(inputfile);
|
|
compiler.OutputFile = Path.GetFileName(outputfile);
|
|
compiler.SourceFile = sourcefile;
|
|
compiler.WorkingDirectory = Path.GetDirectoryName(inputfile);
|
|
if(compiler.Run())
|
|
{
|
|
// Process errors
|
|
foreach(CompilerError e in compiler.Errors)
|
|
{
|
|
CompilerError newerror = e;
|
|
|
|
// If the error's filename equals our temporary file,
|
|
// use the lump name instead and prefix it with ?
|
|
if(string.Compare(e.filename, inputfile, true) == 0)
|
|
newerror.filename = "?" + reallumpname;
|
|
|
|
errors.Add(newerror);
|
|
}
|
|
|
|
// No errors?
|
|
if(compiler.Errors.Length == 0)
|
|
{
|
|
// Output file exists?
|
|
if(File.Exists(outputfile))
|
|
{
|
|
// Copy output file data into a lump?
|
|
if(!string.IsNullOrEmpty(scriptconfig.ResultLump))
|
|
{
|
|
// Do that now then
|
|
try
|
|
{
|
|
filedata = File.ReadAllBytes(outputfile);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Fail
|
|
compiler.Dispose();
|
|
errors.Add(new CompilerError("Unable to read compiler output file. " + e.GetType().Name + ": " + e.Message));
|
|
return false;
|
|
}
|
|
|
|
// Store data
|
|
MemoryStream stream = new MemoryStream(filedata);
|
|
SetLumpData(scriptconfig.ResultLump, stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
compiler.Dispose();
|
|
|
|
// Done
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Fail
|
|
compiler.Dispose();
|
|
errors = null;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No compiler to run for this script type
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// This clears all compiler errors
|
|
internal void ClearCompilerErrors()
|
|
{
|
|
errors.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// This updates everything after the configuration or settings have been changed
|
|
internal void UpdateConfiguration()
|
|
{
|
|
// Update map
|
|
map.UpdateConfiguration();
|
|
|
|
// Update settings
|
|
renderer3d.CreateProjection();
|
|
|
|
// Things filters
|
|
General.MainWindow.UpdateThingsFilters();
|
|
}
|
|
|
|
// This changes thing filter
|
|
public void ChangeThingFilter(ThingsFilter newfilter)
|
|
{
|
|
// We have a special filter for null
|
|
if(newfilter == null) newfilter = new NullThingsFilter();
|
|
|
|
// Deactivate old filter
|
|
if(thingsfilter != null) thingsfilter.Deactivate();
|
|
|
|
// Change
|
|
thingsfilter = newfilter;
|
|
|
|
// Activate filter
|
|
thingsfilter.Activate();
|
|
|
|
// Update interface
|
|
General.MainWindow.ReflectThingsFilter();
|
|
|
|
// Redraw
|
|
General.MainWindow.RedrawDisplay();
|
|
}
|
|
|
|
// This sets a new mapset for editing
|
|
internal void ChangeMapSet(MapSet newmap)
|
|
{
|
|
// Let the plugin and editing mode know
|
|
General.Plugins.OnMapSetChangeBegin();
|
|
if(General.Editing.Mode != null) General.Editing.Mode.OnMapSetChangeBegin();
|
|
this.visualcamera.Sector = null;
|
|
|
|
// Can't have a selection in an old map set
|
|
map.ClearAllSelected();
|
|
|
|
// Reset surfaces
|
|
renderer2d.Surfaces.Reset();
|
|
|
|
// Apply
|
|
map.Dispose();
|
|
map = newmap;
|
|
map.UpdateConfiguration();
|
|
map.SnapAllToAccuracy();
|
|
map.Update();
|
|
thingsfilter.Update();
|
|
|
|
// Let the plugin and editing mode know
|
|
General.Plugins.OnMapSetChangeEnd();
|
|
if(General.Editing.Mode != null) General.Editing.Mode.OnMapSetChangeEnd();
|
|
}
|
|
|
|
// This reloads resources
|
|
[BeginAction("reloadresources")]
|
|
internal void DoReloadResource()
|
|
{
|
|
// Set this to false so we can see if errors are added
|
|
General.ErrorLogger.IsErrorAdded = false;
|
|
|
|
ReloadResources();
|
|
|
|
if(General.ErrorLogger.IsErrorAdded)
|
|
{
|
|
// Show any errors if preferred
|
|
General.MainWindow.DisplayStatus(StatusType.Warning, "There were errors during resources loading!");
|
|
if(General.Settings.ShowErrorsWindow) General.MainWindow.ShowErrors();
|
|
}
|
|
else
|
|
General.MainWindow.DisplayReady();
|
|
|
|
}
|
|
internal void ReloadResources()
|
|
{
|
|
DataLocation maplocation;
|
|
StatusInfo oldstatus;
|
|
Cursor oldcursor;
|
|
|
|
// Keep old display info
|
|
oldstatus = General.MainWindow.Status;
|
|
oldcursor = Cursor.Current;
|
|
|
|
// Show status
|
|
General.MainWindow.DisplayStatus(StatusType.Busy, "Reloading data resources...");
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
// Clean up
|
|
data.Dispose();
|
|
data = null;
|
|
config = null;
|
|
configinfo = null;
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
|
|
// Reload game configuration
|
|
General.WriteLogLine("Reloading game configuration...");
|
|
configinfo = General.GetConfigurationInfo(options.ConfigFile);
|
|
config = new GameConfiguration(General.LoadGameConfiguration(options.ConfigFile));
|
|
General.Editing.UpdateCurrentEditModes();
|
|
|
|
// Reload data resources
|
|
General.WriteLogLine("Reloading data resources...");
|
|
data = new DataManager();
|
|
if(!string.IsNullOrEmpty(filepathname))
|
|
{
|
|
maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, false, false, false);
|
|
data.Load(configinfo.Resources, options.Resources, maplocation);
|
|
}
|
|
else
|
|
{
|
|
data.Load(configinfo.Resources, options.Resources);
|
|
}
|
|
|
|
// Apply new settings to map elements
|
|
map.UpdateConfiguration();
|
|
|
|
// Re-link the background image
|
|
grid.LinkBackground();
|
|
|
|
// Inform all plugins that the resources are reloaded
|
|
General.Plugins.ReloadResources();
|
|
|
|
// Inform editing mode that the resources are reloaded
|
|
if(General.Editing.Mode != null) General.Editing.Mode.OnReloadResources();
|
|
|
|
// Reset status
|
|
General.MainWindow.DisplayStatus(oldstatus);
|
|
Cursor.Current = oldcursor;
|
|
|
|
//mxd
|
|
GZBuilder.GZGeneral.OnReloadResources();
|
|
}
|
|
|
|
// Game Configuration action
|
|
[BeginAction("mapoptions")]
|
|
internal void ShowMapOptions()
|
|
{
|
|
// Cancel volatile mode, if any
|
|
General.Editing.DisengageVolatileMode();
|
|
|
|
// Show map options dialog
|
|
MapOptionsForm optionsform = new MapOptionsForm(options);
|
|
if(optionsform.ShowDialog(General.MainWindow) == DialogResult.OK)
|
|
{
|
|
// Update interface
|
|
General.MainWindow.UpdateInterface();
|
|
|
|
// Stop data manager
|
|
data.Dispose();
|
|
|
|
// Apply new options
|
|
this.options = optionsform.Options;
|
|
|
|
// Load new game configuration
|
|
General.WriteLogLine("Loading game configuration...");
|
|
configinfo = General.GetConfigurationInfo(options.ConfigFile);
|
|
config = new GameConfiguration(General.LoadGameConfiguration(options.ConfigFile));
|
|
configinfo.ApplyDefaults(config);
|
|
General.Editing.UpdateCurrentEditModes();
|
|
|
|
// Setup new map format IO
|
|
General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
|
|
io = MapSetIO.Create(config.FormatInterface, tempwad, this);
|
|
|
|
// Create required lumps if they don't exist yet
|
|
CreateRequiredLumps(tempwad, TEMP_MAP_HEADER);
|
|
|
|
// Let the plugins know
|
|
General.Plugins.MapReconfigure();
|
|
|
|
// Update interface
|
|
General.MainWindow.SetupInterface();
|
|
General.MainWindow.UpdateThingsFilters();
|
|
General.MainWindow.UpdateInterface();
|
|
|
|
// Reload resources
|
|
ReloadResources();
|
|
|
|
// Done
|
|
General.MainWindow.DisplayReady();
|
|
}
|
|
|
|
// Done
|
|
optionsform.Dispose();
|
|
}
|
|
|
|
// This shows the things filters setup
|
|
[BeginAction("thingsfilterssetup")]
|
|
internal void ShowThingsFiltersSetup()
|
|
{
|
|
// Show things filter dialog
|
|
ThingsFiltersForm f = new ThingsFiltersForm();
|
|
f.ShowDialog(General.MainWindow);
|
|
f.Dispose();
|
|
General.MainWindow.UpdateThingsFilters();
|
|
}
|
|
|
|
// This returns true is the given type matches
|
|
public bool IsType(Type t)
|
|
{
|
|
return io.GetType().Equals(t);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|