2015-12-31 12:21:44 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Drawing ;
using System.IO ;
using System.Windows.Forms ;
using CodeImp.DoomBuilder.Actions ;
using CodeImp.DoomBuilder.Compilers ;
using CodeImp.DoomBuilder.Config ;
using CodeImp.DoomBuilder.Data ;
using CodeImp.DoomBuilder.Editing ;
using CodeImp.DoomBuilder.Geometry ;
using CodeImp.DoomBuilder.GZBuilder.Data ; //mxd
using CodeImp.DoomBuilder.GZBuilder.GZDoom ; //mxd
using CodeImp.DoomBuilder.IO ;
using CodeImp.DoomBuilder.Map ;
using CodeImp.DoomBuilder.Rendering ;
using CodeImp.DoomBuilder.VisualModes ;
using CodeImp.DoomBuilder.Windows ;
#endregion
namespace CodeImp.DoomBuilder
{
2016-04-07 13:29:47 +00:00
public sealed class MapManager : IDisposable
2015-12-31 12:21:44 +00:00
{
#region = = = = = = = = = = = = = = = = = = Constants
// Map header name in temporary file
internal const string TEMP_MAP_HEADER = "TEMPMAP" ;
internal const string BUILD_MAP_HEADER = "MAP01" ;
public const string CONFIG_MAP_HEADER = "~MAP" ;
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
// Status
private bool changed ;
private bool scriptschanged ;
private bool maploading ; //mxd
// Map information
private string filetitle ;
private string filepathname ;
private string temppath ;
private string origmapconfigname ; //mxd. Map configuration, which was used to open the map.
// Main objects
private MapSet map ;
private MapSetIO io ;
private MapOptions options ;
private ConfigurationInfo configinfo ;
private GameConfiguration config ;
private DataManager data ;
private D3DDevice graphics ;
private Renderer2D renderer2d ;
private Renderer3D renderer3d ;
private WAD tempwad ;
private GridSetup grid ;
private UndoManager undoredo ;
private CopyPasteManager copypaste ;
private Launcher launcher ;
private ThingsFilter thingsfilter ;
private ScriptEditorForm scriptwindow ;
private List < CompilerError > errors ;
private VisualCamera visualcamera ;
//mxd
private Dictionary < string , ScriptItem > namedscripts ;
private Dictionary < int , ScriptItem > numberedscripts ;
private readonly HashSet < string > scriptincludes ;
// Disposing
private bool isdisposed ;
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
public string FilePathName { get { return filepathname ; } }
public string FileTitle { get { return filetitle ; } internal set { filetitle = value ; } } //mxd. Added setter
public string TempPath { get { return temppath ; } }
public MapOptions Options { get { return options ; } }
public MapSet Map { get { return map ; } }
public DataManager Data { get { return data ; } }
public bool IsChanged { get { return changed | CheckScriptChanged ( ) ; } set { changed | = value ; if ( ! maploading ) General . MainWindow . UpdateMapChangedStatus ( ) ; } }
public bool IsDisposed { get { return isdisposed ; } }
internal D3DDevice Graphics { get { return graphics ; } }
public IRenderer2D Renderer2D { get { return renderer2d ; } }
public IRenderer3D Renderer3D { get { return renderer3d ; } }
internal Renderer2D CRenderer2D { get { return renderer2d ; } }
internal Renderer3D CRenderer3D { get { return renderer3d ; } }
public GameConfiguration Config { get { return config ; } }
public ConfigurationInfo ConfigSettings { get { return configinfo ; } }
public GridSetup Grid { get { return grid ; } }
public UndoManager UndoRedo { get { return undoredo ; } }
internal CopyPasteManager CopyPaste { get { return copypaste ; } }
public IMapSetIO FormatInterface { get { return io ; } }
internal Launcher Launcher { get { return launcher ; } }
public ThingsFilter ThingsFilter { get { return thingsfilter ; } }
internal List < CompilerError > Errors { get { return errors ; } }
internal ScriptEditorForm ScriptEditor { get { return scriptwindow ; } }
public VisualCamera VisualCamera { get { return visualcamera ; } set { visualcamera = value ; } }
public bool IsScriptsWindowOpen { get { return ( scriptwindow ! = null ) & & ! scriptwindow . IsDisposed ; } }
//mxd. Map format
public bool UDMF { get { return config . UDMF ; } }
public bool HEXEN { get { return config . HEXEN ; } }
public bool DOOM { get { return config . DOOM ; } }
2016-01-02 13:21:15 +00:00
public bool SRB2 { get { return config . SRB2 ; } }
2015-12-31 12:21:44 +00:00
//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
2016-04-07 13:29:47 +00:00
public void Dispose ( )
2015-12-31 12:21:44 +00:00
{
// 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" ;
2016-04-07 17:31:06 +00:00
this . filepathname = String . Empty ;
2015-12-31 12:21:44 +00:00
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 ( ) ;
// 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 ;
2016-01-23 02:38:26 +00:00
this . maploading = true ;
2015-12-31 12:21:44 +00:00
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 ( ) ;
2016-01-23 02:38:26 +00:00
// Create MapSet
2015-12-31 12:21:44 +00:00
bool maprestored ;
if ( ! CreateMapSet ( newmap , filepathname , options , out maprestored ) ) return false ;
2016-01-23 02:38:26 +00:00
// And switch to it
2015-12-31 12:21:44 +00:00
ChangeMapSet ( newmap ) ;
2016-01-23 02:38:26 +00:00
// Translate texture names
2015-12-31 12:21:44 +00:00
map . TranslateTextureNames ( config . UseLongTextureNames , false ) ;
grid . TranslateBackgroundName ( config . UseLongTextureNames ) ;
2016-01-23 02:38:26 +00:00
// Sector textures may've been changed
2015-12-31 12:21:44 +00:00
data . UpdateUsedTextures ( ) ;
2016-01-23 08:40:17 +00:00
// This will update DataManager.mapinfo only
data . ReloadMapInfoPartial ( ) ;
2016-01-23 02:38:26 +00:00
// Skybox may've been changed
data . SetupSkybox ( ) ;
2015-12-31 12:21:44 +00:00
2016-01-23 02:38:26 +00:00
// Update includes list and script names
UpdateScriptNames ( true ) ;
// Restore selection groups
2015-12-31 12:21:44 +00:00
options . ReadSelectionGroups ( ) ;
2016-01-23 02:38:26 +00:00
// Center map in screen or on stored coordinates
2015-12-31 12:21:44 +00:00
if ( General . Editing . Mode is ClassicMode )
{
ClassicMode mode = General . Editing . Mode as ClassicMode ;
mode . OnRedoEnd ( ) ;
if ( options . ViewPosition . IsFinite ( ) & & ! float . IsNaN ( options . ViewScale ) )
mode . CenterOnCoordinates ( options . ViewPosition , options . ViewScale ) ;
else
mode . CenterInScreen ( ) ;
}
// Success
this . changed = maprestored ;
2016-01-23 02:38:26 +00:00
this . maploading = false ;
2015-12-31 12:21:44 +00:00
General . WriteLogLine ( "Map switching done" ) ;
2016-01-23 02:38:26 +00:00
General . MainWindow . UpdateMapChangedStatus ( ) ;
2015-12-31 12:21:44 +00:00
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 )
{
2016-01-25 16:04:07 +00:00
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 )
{
2016-02-29 10:41:17 +00:00
if ( t . SRB2Type = = config . Start3DModeThingType )
2016-01-25 16:04:07 +00:00
{
// 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
General . MainWindow . DisplayStatus ( StatusType . Busy , "Compressing sidedefs..." ) ;
outputset . CompressSidedefs ( ) ;
// 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 ) ;
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 )
2015-12-31 12:21:44 +00:00
{
string settingsfile ;
WAD targetwad ;
bool includenodes ;
General . WriteLogLine ( "Saving map to file: " + newfilepathname ) ;
// Scripts changed?
bool localscriptschanged = CheckScriptChanged ( ) ;
// If the scripts window is open, save the scripts first
if ( IsScriptsWindowOpen ) scriptwindow . Editor . ImplicitSave ( ) ;
// Only recompile scripts when the scripts have changed
// (not when only the map changed)
if ( localscriptschanged & & ! CompileScriptLumps ( ) )
{
// Compiler failure
if ( errors . Count > 0 )
General . ShowErrorMessage ( "Error while compiling scripts: " + errors [ 0 ] . description , MessageBoxButtons . OK ) ;
else
General . ShowErrorMessage ( "Unknown compiler error while compiling scripts!" , MessageBoxButtons . OK ) ;
}
// Show script window if there are any errors and we are going to test the map
// and always update the errors on the scripts window.
if ( ( errors . Count > 0 ) & & ( scriptwindow = = null ) & & ( purpose = = SavePurpose . Testing ) ) ShowScriptEditor ( ) ;
if ( scriptwindow ! = null ) scriptwindow . Editor . ShowErrors ( errors ) ;
// Only write the map and rebuild nodes when the actual map has changed
// (not when only scripts have changed)
if ( changed )
{
2016-01-25 16:04:07 +00:00
// Write the current map structures to the temp file
if ( ! WriteMapToTempFile ( ) ) return false ;
2015-12-31 12:21:44 +00:00
2016-01-25 16:04:07 +00:00
// Get the corresponding nodebuilder
string nodebuildername = ( purpose = = SavePurpose . Testing ) ? configinfo . NodebuilderTest : configinfo . NodebuilderSave ;
2015-12-31 12:21:44 +00:00
2016-01-25 16:04:07 +00:00
// Build the nodes
StatusInfo oldstatus = General . MainWindow . Status ;
General . MainWindow . DisplayStatus ( StatusType . Busy , "Building map nodes..." ) ;
2015-12-31 12:21:44 +00:00
includenodes = ( ! string . IsNullOrEmpty ( nodebuildername ) & & BuildNodes ( nodebuildername , true ) ) ;
General . MainWindow . DisplayStatus ( oldstatus ) ;
}
else
{
// Check if we have nodebuilder lumps
includenodes = VerifyNodebuilderLumps ( tempwad , TEMP_MAP_HEADER ) ;
}
// Suspend data resources
data . Suspend ( ) ;
//mxd. Check if the target file is locked
FileLockChecker . FileLockCheckResult checkresult = FileLockChecker . CheckFile ( newfilepathname ) ;
if ( ! string . IsNullOrEmpty ( checkresult . Error ) )
{
if ( checkresult . Processes . Count > 0 )
{
string rest = "Press 'Retry' to close " + ( checkresult . Processes . Count > 1 ? "all processes" : "the process" )
+ " and retry." + Environment . NewLine + "Press 'Cancel' to cancel saving." ;
if ( General . ShowErrorMessage ( checkresult . Error + rest , MessageBoxButtons . RetryCancel ) = = DialogResult . Retry )
{
// Close all processes
foreach ( Process process in checkresult . Processes )
{
try
{
if ( ! process . HasExited ) process . Kill ( ) ;
}
catch ( Exception e )
{
General . ShowErrorMessage ( "Failed to close " + Path . GetFileName ( process . MainModule . FileName ) + ":" + Environment . NewLine + Environment . NewLine + e . Message , MessageBoxButtons . OK ) ;
data . Resume ( ) ;
General . WriteLogLine ( "Map saving failed: failed to close " + Path . GetFileName ( process . MainModule . FileName ) ) ;
return false ;
}
}
// Retry
data . Resume ( ) ;
General . WriteLogLine ( "Map saving restarted..." ) ;
return SaveMap ( newfilepathname , purpose ) ;
}
else
{
data . Resume ( ) ;
General . WriteLogLine ( "Map saving cancelled..." ) ;
return false ;
}
}
else
{
General . ShowErrorMessage ( checkresult . Error , MessageBoxButtons . OK ) ;
data . Resume ( ) ;
General . WriteLogLine ( "Map saving failed: " + checkresult . Error ) ;
return false ;
}
}
2016-04-07 13:29:47 +00:00
// Determine original map name
string origmapname = ( ! string . IsNullOrEmpty ( options . PreviousName ) & & purpose ! = SavePurpose . IntoFile ) ? options . PreviousName : options . CurrentName ;
2015-12-31 12:21:44 +00:00
string origwadfile = string . Empty ; //mxd
try
{
if ( File . Exists ( newfilepathname ) )
{
// mxd. Check if target wad already has a map with the same name
if ( purpose = = SavePurpose . IntoFile )
{
WAD wad = new WAD ( newfilepathname , true ) ;
int mapindex = wad . FindLumpIndex ( origmapname ) ;
wad . Dispose ( ) ;
if ( mapindex ! = - 1 & & MessageBox . Show ( General . MainWindow , "Target file already contains map '" + origmapname + "'\nDo you want to replace it?" , "Map already exists!" , MessageBoxButtons . YesNo , MessageBoxIcon . Warning ) = = DialogResult . No )
{
data . Resume ( ) ;
General . WriteLogLine ( "Map saving cancelled..." ) ;
return false ;
}
}
2016-01-10 19:10:35 +00:00
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" ) ;
}
}
2015-12-31 12:21:44 +00:00
// 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
2016-04-07 13:29:47 +00:00
if ( ( purpose = = SavePurpose . AsNewFile ) & & ( ! String . IsNullOrEmpty ( filepathname ) ) )
2015-12-31 12:21:44 +00:00
{
// Copy if original file still exists
if ( File . Exists ( filepathname ) ) File . Copy ( filepathname , newfilepathname , true ) ;
}
// If the target file exists, we need to rebuild it
if ( File . Exists ( newfilepathname ) )
{
// Move the target file aside
origwadfile = newfilepathname + ".temp" ;
File . Move ( newfilepathname , origwadfile ) ;
// Open original file
WAD origwad = new WAD ( origwadfile , true ) ;
// Create new target file
targetwad = new WAD ( newfilepathname ) ;
// Copy all lumps, except the original map
GameConfiguration origcfg ; //mxd
if ( origmapconfigname = = configinfo . Filename )
{
origcfg = config ;
}
else
{
ConfigurationInfo ci = General . GetConfigurationInfo ( origmapconfigname ) ;
origcfg = new GameConfiguration ( ci . Configuration ) ;
}
CopyAllLumpsExceptMap ( origwad , targetwad , origcfg , origmapname ) ;
// Close original file and delete it
origwad . Dispose ( ) ;
File . Delete ( origwadfile ) ;
}
else
{
// Create new target file
targetwad = new WAD ( newfilepathname ) ;
}
}
catch ( IOException )
{
General . ShowErrorMessage ( "IO Error while writing target file: " + newfilepathname + ". Please make sure the location is accessible and not in use by another program." , MessageBoxButtons . OK ) ;
if ( ! string . IsNullOrEmpty ( origwadfile ) & & File . Exists ( origwadfile ) ) File . Delete ( origwadfile ) ; //mxd. Clean-up
data . Resume ( ) ;
General . WriteLogLine ( "Map saving failed" ) ;
return false ;
}
catch ( UnauthorizedAccessException )
{
General . ShowErrorMessage ( "Error while accessing target file: " + newfilepathname + ". Please make sure the location is accessible and not in use by another program." , MessageBoxButtons . OK ) ;
if ( ! string . IsNullOrEmpty ( origwadfile ) & & File . Exists ( origwadfile ) ) File . Delete ( origwadfile ) ; //mxd. Clean-up
data . Resume ( ) ;
General . WriteLogLine ( "Map saving failed" ) ;
return false ;
}
// Copy map lumps to target file
CopyLumpsByType ( tempwad , TEMP_MAP_HEADER , targetwad , origmapname , true , true , includenodes , true ) ;
// mxd. Was the map renamed?
if ( options . LevelNameChanged )
{
if ( purpose ! = SavePurpose . IntoFile )
{
General . WriteLogLine ( "Changing map name from '" + options . PreviousName + "' to '" + options . CurrentName + "'" ) ;
// Find the map header in target
2016-01-25 16:04:07 +00:00
int index = targetwad . FindLumpIndex ( options . PreviousName ) ;
2015-12-31 12:21:44 +00:00
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 ;
}
}
2016-04-07 17:31:06 +00:00
options . PreviousName = String . Empty ;
2015-12-31 12:21:44 +00:00
}
// 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
}
2016-01-25 16:04:07 +00:00
#endregion
2015-12-31 12:21:44 +00:00
2016-01-25 16:04:07 +00:00
#region = = = = = = = = = = = = = = = = = = Nodebuild
2015-12-31 12:21:44 +00:00
2016-01-25 16:04:07 +00:00
/// <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 )
2015-12-31 12:21:44 +00:00
{
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
2016-04-07 17:31:06 +00:00
string compilererrors = String . Empty ;
2015-12-31 12:21:44 +00:00
foreach ( CompilerError e in compiler . Errors )
compilererrors + = Environment . NewLine + e . description ;
// Nodebuilder did not build the lumps!
if ( failaswarning )
2016-04-07 17:31:06 +00:00
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 ) ;
2015-12-31 12:21:44 +00:00
else
2016-04-07 17:31:06 +00:00
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 ) ;
2015-12-31 12:21:44 +00:00
}
// Done with the build wad
if ( buildwad ! = null ) buildwad . Dispose ( ) ;
}
else //mxd
{
//collect errors
2016-04-07 17:31:06 +00:00
string compilererrors = String . Empty ;
2015-12-31 12:21:44 +00:00
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 ( ) ;
2016-01-25 16:04:07 +00:00
// Let the plugins know
if ( lumpscomplete ) General . Plugins . OnMapNodesRebuilt ( ) ;
// Return result
return lumpscomplete ;
2015-12-31 12:21:44 +00:00
}
}
// 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 ) ;
}
else
{
// Move insert index
insertindex = targetindex ;
}
}
}
}
//mxd. This is called on tempwad, which should only have the current map inside it.
private void RemoveUnneededLumps ( WAD target , string mapname , bool glnodesonly )
{
//Get the list of lumps required by current map format
List < string > requiredLumps = new List < string > ( ) ;
foreach ( KeyValuePair < string , MapLumpInfo > group in config . MapLumps )
{
//this lump well be recreated by a nodebuilder when saving the map
//(or it won't be if the new map format or nodebuilder doesn't require / build this lump,
//so it will just stay there, possibly messing things up)
2016-09-17 16:00:11 +00:00
if ( group . Value . NodeBuild & & ( ! glnodesonly | | group . Key . ToUpperInvariant ( ) . StartsWith ( "GL_" ) ) ) continue ;
//Skip lumps that aren't required.
if ( ! group . Value . Required ) continue ;
2015-12-31 12:21:44 +00:00
string lumpname = group . Key ;
if ( lumpname = = CONFIG_MAP_HEADER ) lumpname = mapname ;
requiredLumps . Add ( lumpname ) ;
}
//Remove lumps, which are not required
List < Lump > toRemove = new List < Lump > ( ) ;
foreach ( Lump lump in target . Lumps )
if ( ! requiredLumps . Contains ( lump . Name ) ) toRemove . Add ( lump ) ;
foreach ( Lump lump in toRemove ) target . Remove ( lump ) ;
}
// This copies all lumps, except those of a specific map
private static void CopyAllLumpsExceptMap ( WAD source , WAD target , GameConfiguration mapconfig , string sourcemapname )
{
// Go for all lumps
bool skipping = false ;
foreach ( Lump srclump in source . Lumps )
{
// Check if we should stop skipping lumps here
if ( skipping )
{
//mxd
string srclumpname = srclump . Name ;
if ( srclumpname . Contains ( sourcemapname ) ) srclumpname = srclumpname . Replace ( sourcemapname , CONFIG_MAP_HEADER ) ;
if ( ! mapconfig . MapLumps . ContainsKey ( srclumpname ) )
{
// Stop skipping
skipping = false ;
}
}
// Check if we should start skipping lumps here
if ( ! skipping & & ( srclump . Name = = sourcemapname ) )
{
// We have encountered the map header, start skipping!
skipping = true ;
}
// Not skipping this lump?
if ( ! skipping )
{
// Copy lump over!
Lump tgtlump = target . Insert ( srclump . Name , target . Lumps . Count , srclump . Length ) ;
srclump . CopyTo ( tgtlump ) ;
}
}
}
// This copies specific map lumps from one WAD to another
private void CopyLumpsByType ( WAD source , string sourcemapname ,
WAD target , string targetmapname ,
bool copyrequired , bool copyblindcopy ,
bool copynodebuild , bool copyscript )
{
// Find the map header in target
int tgtheaderindex = target . FindLumpIndex ( targetmapname ) ;
if ( tgtheaderindex = = - 1 )
{
// If this header doesnt exists in the target
// then insert at the end of the target
tgtheaderindex = target . Lumps . Count ;
}
// Begin inserting at target header index
int targetindex = tgtheaderindex ;
// Find the map header in source
int srcheaderindex = source . FindLumpIndex ( sourcemapname ) ;
if ( srcheaderindex > - 1 )
{
// Go for all the map lump names
foreach ( KeyValuePair < string , MapLumpInfo > group in config . MapLumps )
{
// Check if this lump should be copied
if ( ( group . Value . Required & & copyrequired ) | | ( group . Value . BlindCopy & & copyblindcopy ) | |
( group . Value . NodeBuild & & copynodebuild ) | | ( ( group . Value . Script ! = null | | group . Value . ScriptBuild ) & & copyscript ) )
{
// Get the lump name
string srclumpname = ( group . Key . Contains ( CONFIG_MAP_HEADER ) ? group . Key . Replace ( CONFIG_MAP_HEADER , sourcemapname ) : group . Key ) ; //mxd
string tgtlumpname = ( group . Key . Contains ( CONFIG_MAP_HEADER ) ? group . Key . Replace ( CONFIG_MAP_HEADER , targetmapname ) : group . Key ) ; //mxd
// Find the lump in the source
int sourceindex = FindSpecificLump ( source , srclumpname , srcheaderindex , sourcemapname , config . MapLumps ) ;
if ( sourceindex > - 1 )
{
// Remove lump at target
int lumpindex = RemoveSpecificLump ( target , tgtlumpname , tgtheaderindex , targetmapname , config . MapLumps ) ;
// Determine target index
// When original lump was found and removed then insert at that position
// otherwise insert after last insertion position
if ( lumpindex > - 1 ) targetindex = lumpindex ; else targetindex + + ;
if ( targetindex > target . Lumps . Count ) targetindex = target . Lumps . Count ;
// Copy the lump to the target
//General.WriteLogLine(srclumpname + " copying as " + tgtlumpname);
Lump lump = source . Lumps [ sourceindex ] ;
Lump newlump = target . Insert ( tgtlumpname , targetindex , lump . Length ) ;
lump . CopyTo ( newlump ) ;
}
else
{
// We don't want to bother the user with this. There are a lot of lumps in
// the game configs that are trivial and don't need to be found.
if ( group . Value . Required )
{
General . ErrorLogger . Add ( ErrorType . Warning , group . Key + " (required lump) should be read but was not found in the WAD file." ) ;
}
}
}
}
}
}
// This finds a lump within the range of known lump names
// Returns -1 when the lump cannot be found
private static int FindSpecificLump ( WAD source , string lumpname , int mapheaderindex , string mapheadername , Dictionary < string , MapLumpInfo > maplumps )
{
// Use the configured map lump names to find the specific lump within range,
// because when an unknown lump is met, this search must stop.
// Go for all lumps in order to find the specified lump
for ( int i = 0 ; i < maplumps . Count + 1 ; i + + )
{
// Still within bounds?
if ( ( mapheaderindex + i ) < source . Lumps . Count )
{
// Check if this is a known lump name
string srclumpname = source . Lumps [ mapheaderindex + i ] . Name ; //mxd
if ( srclumpname . Contains ( mapheadername ) ) srclumpname = srclumpname . Replace ( mapheadername , CONFIG_MAP_HEADER ) ;
if ( maplumps . ContainsKey ( srclumpname ) ) //mxd
{
// Is this the lump we are looking for?
if ( source . Lumps [ mapheaderindex + i ] . Name = = lumpname )
{
// Return this index
return mapheaderindex + i ;
}
}
else
{
// Unknown lump hit, abort search
break ;
}
}
}
// Nothing found
return - 1 ;
}
// This removes a specific lump and returns the position where the lump was removed
// Returns -1 when the lump could not be found
internal static int RemoveSpecificLump ( WAD source , string lumpname , int mapheaderindex , string mapheadername , Dictionary < string , MapLumpInfo > maplumps )
{
// Find the specific lump index
int lumpindex = FindSpecificLump ( source , lumpname , mapheaderindex , mapheadername , maplumps ) ;
if ( lumpindex > - 1 )
{
// Remove this lump
//General.WriteLogLine(lumpname + " removed");
source . RemoveAt ( lumpindex ) ;
}
/ * else
{
// Lump not found
General . ErrorLogger . Add ( ErrorType . Warning , lumpname + " should be removed but was not found!" ) ;
} * /
// Return result
return lumpindex ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Selection Groups
// This adds selection to a group
private void AddSelectionToGroup ( int groupindex )
{
General . Interface . SetCursor ( Cursors . WaitCursor ) ;
// Make selection
map . AddSelectionToGroup ( groupindex ) ; //mxd. switched groupmask to groupindex
General . Interface . DisplayStatus ( StatusType . Action , "Assigned selection to group " + ( groupindex + 1 ) ) ;
General . Interface . SetCursor ( Cursors . Default ) ;
}
// This selects a group
private void SelectGroup ( int groupindex )
{
// Select
int groupmask = 0x01 < < groupindex ;
map . SelectVerticesByGroup ( groupmask ) ;
map . SelectLinedefsByGroup ( groupmask ) ;
map . SelectSectorsByGroup ( groupmask ) ;
map . SelectThingsByGroup ( groupmask ) ;
// Redraw to show selection
General . Interface . DisplayStatus ( StatusType . Action , "Selected group " + ( groupindex + 1 ) ) ;
General . Interface . RedrawDisplay ( ) ;
}
//mxd. This clears a group
private void ClearGroup ( int groupindex )
{
General . Interface . SetCursor ( Cursors . WaitCursor ) ;
// Clear group
map . ClearGroup ( 0x01 < < groupindex ) ;
General . Interface . DisplayStatus ( StatusType . Action , "Cleared group " + ( groupindex + 1 ) ) ;
General . Interface . SetCursor ( Cursors . Default ) ;
}
// Select actions
[BeginAction("selectgroup1")]
internal void SelectGroup1 ( ) { SelectGroup ( 0 ) ; }
[BeginAction("selectgroup2")]
internal void SelectGroup2 ( ) { SelectGroup ( 1 ) ; }
[BeginAction("selectgroup3")]
internal void SelectGroup3 ( ) { SelectGroup ( 2 ) ; }
[BeginAction("selectgroup4")]
internal void SelectGroup4 ( ) { SelectGroup ( 3 ) ; }
[BeginAction("selectgroup5")]
internal void SelectGroup5 ( ) { SelectGroup ( 4 ) ; }
[BeginAction("selectgroup6")]
internal void SelectGroup6 ( ) { SelectGroup ( 5 ) ; }
[BeginAction("selectgroup7")]
internal void SelectGroup7 ( ) { SelectGroup ( 6 ) ; }
[BeginAction("selectgroup8")]
internal void SelectGroup8 ( ) { SelectGroup ( 7 ) ; }
[BeginAction("selectgroup9")]
internal void SelectGroup9 ( ) { SelectGroup ( 8 ) ; }
[BeginAction("selectgroup10")]
internal void SelectGroup10 ( ) { SelectGroup ( 9 ) ; }
// Assign actions
[BeginAction("assigngroup1")]
internal void AssignGroup1 ( ) { AddSelectionToGroup ( 0 ) ; }
[BeginAction("assigngroup2")]
internal void AssignGroup2 ( ) { AddSelectionToGroup ( 1 ) ; }
[BeginAction("assigngroup3")]
internal void AssignGroup3 ( ) { AddSelectionToGroup ( 2 ) ; }
[BeginAction("assigngroup4")]
internal void AssignGroup4 ( ) { AddSelectionToGroup ( 3 ) ; }
[BeginAction("assigngroup5")]
internal void AssignGroup5 ( ) { AddSelectionToGroup ( 4 ) ; }
[BeginAction("assigngroup6")]
internal void AssignGroup6 ( ) { AddSelectionToGroup ( 5 ) ; }
[BeginAction("assigngroup7")]
internal void AssignGroup7 ( ) { AddSelectionToGroup ( 6 ) ; }
[BeginAction("assigngroup8")]
internal void AssignGroup8 ( ) { AddSelectionToGroup ( 7 ) ; }
[BeginAction("assigngroup9")]
internal void AssignGroup9 ( ) { AddSelectionToGroup ( 8 ) ; }
[BeginAction("assigngroup10")]
internal void AssignGroup10 ( ) { AddSelectionToGroup ( 9 ) ; }
//mxd. Clear actions
[BeginAction("cleargroup1")]
internal void ClearGroup1 ( ) { ClearGroup ( 0 ) ; }
[BeginAction("cleargroup2")]
internal void ClearGroup2 ( ) { ClearGroup ( 1 ) ; }
[BeginAction("cleargroup3")]
internal void ClearGroup3 ( ) { ClearGroup ( 2 ) ; }
[BeginAction("cleargroup4")]
internal void ClearGroup4 ( ) { ClearGroup ( 3 ) ; }
[BeginAction("cleargroup5")]
internal void ClearGroup5 ( ) { ClearGroup ( 4 ) ; }
[BeginAction("cleargroup6")]
internal void ClearGroup6 ( ) { ClearGroup ( 5 ) ; }
[BeginAction("cleargroup7")]
internal void ClearGroup7 ( ) { ClearGroup ( 6 ) ; }
[BeginAction("cleargroup8")]
internal void ClearGroup8 ( ) { ClearGroup ( 7 ) ; }
[BeginAction("cleargroup9")]
internal void ClearGroup9 ( ) { ClearGroup ( 8 ) ; }
[BeginAction("cleargroup10")]
internal void ClearGroup10 ( ) { ClearGroup ( 9 ) ; }
2016-04-07 13:29:47 +00:00
#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 ;
}
}
2015-12-31 12:21:44 +00:00
2016-04-07 13:29:47 +00:00
General . MainWindow . RedrawDisplay ( ) ;
General . MainWindow . UpdateGZDoomPanel ( ) ;
}
2015-12-31 12:21:44 +00:00
2016-04-07 13:29:47 +00:00
[BeginAction("gztogglefog")]
internal void ToggleFog ( )
{
General . Settings . GZDrawFog = ! General . Settings . GZDrawFog ;
General . MainWindow . DisplayStatus ( StatusType . Action , "Fog rendering is " + ( General . Settings . GZDrawFog ? "ENABLED" : "DISABLED" ) ) ;
General . MainWindow . RedrawDisplay ( ) ;
General . MainWindow . UpdateGZDoomPanel ( ) ;
}
[BeginAction("gztogglesky")]
internal void ToggleSky ( )
{
General . Settings . GZDrawSky = ! General . Settings . GZDrawSky ;
General . MainWindow . DisplayStatus ( StatusType . Action , "Sky rendering is " + ( General . Settings . GZDrawSky ? "ENABLED" : "DISABLED" ) ) ;
General . MainWindow . RedrawDisplay ( ) ;
General . MainWindow . UpdateGZDoomPanel ( ) ;
}
[BeginAction("gztogglefx")]
internal void ToggleFx ( )
{
int on = 0 ;
on + = General . Settings . GZDrawFog ? 1 : - 1 ;
on + = General . Settings . GZDrawSky ? 1 : - 1 ;
on + = General . Settings . GZDrawLightsMode ! = LightRenderMode . NONE ? 1 : - 1 ;
on + = General . Settings . GZDrawModelsMode ! = ModelRenderMode . NONE ? 1 : - 1 ;
bool enable = ( on < 0 ) ;
General . Settings . GZDrawFog = enable ;
General . Settings . GZDrawSky = enable ;
General . Settings . GZDrawLightsMode = ( enable ? LightRenderMode . ALL : LightRenderMode . NONE ) ;
General . Settings . GZDrawModelsMode = ( enable ? ModelRenderMode . ALL : ModelRenderMode . NONE ) ;
General . MainWindow . DisplayStatus ( StatusType . Action , "Advanced effects are " + ( enable ? "ENABLED" : "DISABLED" ) ) ;
General . MainWindow . RedrawDisplay ( ) ;
General . MainWindow . UpdateGZDoomPanel ( ) ;
}
[BeginAction("gztoggleeventlines")]
internal void ToggleEventLines ( )
{
General . Settings . GZShowEventLines = ! General . Settings . GZShowEventLines ;
General . MainWindow . DisplayStatus ( StatusType . Action , "Event lines are " + ( General . Settings . GZShowEventLines ? "ENABLED" : "DISABLED" ) ) ;
General . MainWindow . RedrawDisplay ( ) ;
General . MainWindow . UpdateGZDoomPanel ( ) ;
}
[BeginAction("gztogglevisualvertices")]
internal void ToggleVisualVertices ( )
{
General . Settings . GZShowVisualVertices = ! General . Settings . GZShowVisualVertices ;
General . MainWindow . DisplayStatus ( StatusType . Action , "Visual vertices are " + ( General . Settings . GZShowVisualVertices ? "ENABLED" : "DISABLED" ) ) ;
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")]
2015-12-31 12:21:44 +00:00
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 )
{
2016-04-07 17:31:06 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "ACS error in '" + ( error . filename . StartsWith ( "?" ) ? error . filename . Replace ( "?" , String . Empty ) : error . filename )
2015-12-31 12:21:44 +00:00
+ ( 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 ;
}
2016-04-07 13:29:47 +00:00
}
2015-12-31 12:21:44 +00:00
}
}
// 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 ( ) ;
// 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
2016-01-23 08:47:27 +00:00
//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 ( ) ;
2015-12-31 12:21:44 +00:00
}
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 ( ) ;
}
2016-01-23 08:47:27 +00:00
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 ( ) ;
2015-12-31 12:21:44 +00:00
2016-01-23 01:36:35 +00:00
// Reload resources
ReloadResources ( ) ;
// Update interface
General . MainWindow . SetupInterface ( ) ;
2015-12-31 12:21:44 +00:00
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
public SizeF GetTextSize ( string text , float scale )
{
return graphics . Font . GetTextSize ( text , scale ) ;
}
//mxd
[BeginAction("snapvertstogrid")]
private void SnapSelectedMapElementsToGrid ( )
{
// Get selected elements
ICollection < Vertex > verts = map . GetSelectedVertices ( true ) ;
ICollection < Linedef > lines = map . GetSelectedLinedefs ( true ) ; // Sector lines are auto-selected when a sector is selected
ICollection < Thing > things = map . GetSelectedThings ( true ) ;
// Get vertices from selection
Dictionary < int , Vertex > vertstosnap = new Dictionary < int , Vertex > ( verts . Count ) ;
foreach ( Vertex v in verts ) vertstosnap . Add ( v . Index , v ) ;
foreach ( Linedef l in lines )
{
if ( ! vertstosnap . ContainsKey ( l . Start . Index ) ) vertstosnap . Add ( l . Start . Index , l . Start ) ;
if ( ! vertstosnap . ContainsKey ( l . End . Index ) ) vertstosnap . Add ( l . End . Index , l . End ) ;
}
// Anything to snap?
if ( vertstosnap . Count = = 0 & & things . Count = = 0 )
{
General . Interface . DisplayStatus ( StatusType . Warning , "Select any map element first!" ) ;
return ;
}
// Make undo
undoredo . CreateUndo ( "Snap map elements to grid" ) ;
// Do the snapping
Cursor . Current = Cursors . AppStarting ;
// Snap vertices?
int snappedverts = ( vertstosnap . Count > 0 ? SnapVertices ( vertstosnap . Values ) : 0 ) ;
// Snap things?..
int snappedthings = ( things . Count > 0 ? SnapThings ( things ) : 0 ) ;
// Assemble status message
List < string > message = new List < string > ( ) ;
if ( snappedverts > 0 ) message . Add ( snappedverts + " vertices" ) ;
if ( snappedthings > 0 ) message . Add ( snappedthings + " things" ) ;
// Map changed?
if ( message . Count > 0 )
{
// Display status
General . Interface . DisplayStatus ( StatusType . Info , "Snapped " + string . Join ( " and " , message . ToArray ( ) ) ) ;
// Warn the user
/ * if ( snappedverts > 0 )
{
MessageBox . Show ( "Snapped " + snappedverts + " vertices to grid." + Environment . NewLine +
"It's a good idea to run Map Analysis Mode now." ) ;
} * /
// Invoke clear selection to update sector highlight overlay
General . Actions . InvokeAction ( "builder_clearselection" ) ;
// Update cached values
General . Map . Map . Update ( ) ;
// Map is changed
General . Map . IsChanged = true ;
}
else
{
// Display status
General . Interface . DisplayStatus ( StatusType . Info , "Selected map elements were already on the grid." ) ;
// Withdraw undo
undoredo . WithdrawUndo ( ) ;
}
// Done
General . Interface . RedrawDisplay ( ) ;
Cursor . Current = Cursors . Default ;
}
//mxd
private int SnapVertices ( IEnumerable < Vertex > verts )
{
int snappedCount = 0 ;
List < Vertex > movedVerts = new List < Vertex > ( ) ;
List < Linedef > movedLines = new List < Linedef > ( ) ;
//snap them all!
foreach ( Vertex v in verts )
{
Vector2D pos = v . Position ;
v . SnapToGrid ( ) ;
if ( v . Position . x ! = pos . x | | v . Position . y ! = pos . y )
{
snappedCount + + ;
movedVerts . Add ( v ) ;
foreach ( Linedef l in v . Linedefs )
{
if ( ! movedLines . Contains ( l ) ) movedLines . Add ( l ) ;
}
}
}
//Create blockmap
RectangleF area = MapSet . CreateArea ( General . Map . Map . Vertices ) ;
BlockMap < BlockEntry > blockmap = new BlockMap < BlockEntry > ( area ) ;
blockmap . AddVerticesSet ( General . Map . Map . Vertices ) ;
//merge overlapping vertices using teh power of BLOCKMAP!!!11
foreach ( Vertex v in movedVerts )
{
BlockEntry block = blockmap . GetBlockAt ( v . Position ) ;
if ( block = = null ) continue ;
foreach ( Vertex blockVert in block . Vertices )
{
if ( blockVert . IsDisposed | | blockVert . Index = = v . Index | | blockVert . Position ! = v . Position )
continue ;
foreach ( Linedef l in blockVert . Linedefs )
{
if ( ! movedLines . Contains ( l ) ) movedLines . Add ( l ) ;
}
v . Join ( blockVert ) ;
break ;
}
}
// Update cached values of lines because we may need their length/angle
General . Map . Map . Update ( true , false ) ;
General . Map . Map . BeginAddRemove ( ) ;
MapSet . RemoveLoopedLinedefs ( movedLines ) ;
MapSet . JoinOverlappingLines ( movedLines ) ;
General . Map . Map . EndAddRemove ( ) ;
//get changed sectors
List < Sector > changedSectors = new List < Sector > ( ) ;
foreach ( Linedef l in movedLines )
{
if ( l = = null | | l . IsDisposed ) continue ;
if ( l . Front ! = null & & l . Front . Sector ! = null & & ! changedSectors . Contains ( l . Front . Sector ) )
changedSectors . Add ( l . Front . Sector ) ;
if ( l . Back ! = null & & l . Back . Sector ! = null & & ! changedSectors . Contains ( l . Back . Sector ) )
changedSectors . Add ( l . Back . Sector ) ;
}
// Now update area of sectors
General . Map . Map . Update ( false , true ) ;
//fix invalid sectors
foreach ( Sector s in changedSectors )
{
if ( s . BBox . IsEmpty )
{
s . Dispose ( ) ;
}
else if ( s . Sidedefs . Count < 3 )
{
bool merged = false ;
foreach ( Sidedef side in s . Sidedefs )
{
if ( side . Other ! = null & & side . Other . Sector ! = null )
{
s . Join ( side . Other . Sector ) ;
merged = true ;
break ;
}
}
//oh well, I don't know what else I can do here...
if ( ! merged ) s . Dispose ( ) ;
}
}
return snappedCount ;
}
//mxd
private static int SnapThings ( IEnumerable < Thing > things )
{
int snappedCount = 0 ;
//snap them all!
foreach ( Thing t in things )
{
Vector2D pos = t . Position ;
t . SnapToGrid ( ) ;
if ( t . Position . x ! = pos . x | | t . Position . y ! = pos . y ) snappedCount + + ;
}
return snappedCount ;
}
#endregion
}
}