2020-09-11 23:46:04 +03:00
//
// Copyright 2020 Electronic Arts Inc.
//
2023-06-09 11:44:39 +02:00
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
2020-09-11 23:46:04 +03:00
// either version 3 of the License, or (at your option) any later version.
2023-06-09 11:44:39 +02:00
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
2020-09-11 23:46:04 +03:00
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
2020-09-14 18:13:57 +03:00
using MobiusEditor.Controls ;
2020-09-11 23:46:04 +03:00
using MobiusEditor.Dialogs ;
using MobiusEditor.Event ;
using MobiusEditor.Interface ;
using MobiusEditor.Model ;
2022-09-01 13:05:49 +02:00
using MobiusEditor.Tools ;
2020-09-11 23:46:04 +03:00
using MobiusEditor.Tools.Dialogs ;
using MobiusEditor.Utility ;
using Steamworks ;
using System ;
using System.Collections.Generic ;
using System.Data ;
2023-10-18 18:05:23 +02:00
using System.Diagnostics ;
2020-09-11 23:46:04 +03:00
using System.Drawing ;
2023-01-10 19:58:20 +01:00
using System.Drawing.Imaging ;
2020-09-11 23:46:04 +03:00
using System.IO ;
using System.Linq ;
2023-10-18 18:05:23 +02:00
using System.Net ;
using System.Net.Http ;
using System.Net.Http.Headers ;
2022-11-18 15:18:52 +01:00
using System.Numerics ;
2022-08-08 11:53:51 +02:00
using System.Reflection ;
2020-09-11 23:46:04 +03:00
using System.Text ;
2022-11-29 14:11:35 +01:00
using System.Text.RegularExpressions ;
2020-09-11 23:46:04 +03:00
using System.Windows.Forms ;
namespace MobiusEditor
{
2022-10-02 12:40:55 +02:00
public partial class MainForm : Form , IFeedBackHandler , IHasStatusLabel
2020-09-11 23:46:04 +03:00
{
2023-02-23 00:00:54 +01:00
public delegate Object FunctionInvoker ( ) ;
2023-06-27 11:07:57 +02:00
private Dictionary < string , Bitmap > theaterIcons = new Dictionary < string , Bitmap > ( ) ;
2022-09-01 13:05:49 +02:00
2020-09-11 23:46:04 +03:00
private static readonly ToolType [ ] toolTypes ;
private ToolType availableToolTypes = ToolType . None ;
private ToolType activeToolType = ToolType . None ;
private ToolType ActiveToolType
{
get = > activeToolType ;
set
{
var firstAvailableTool = value ;
if ( ( availableToolTypes & firstAvailableTool ) = = ToolType . None )
{
var otherAvailableToolTypes = toolTypes . Where ( t = > ( availableToolTypes & t ) ! = ToolType . None ) ;
firstAvailableTool = otherAvailableToolTypes . Any ( ) ? otherAvailableToolTypes . First ( ) : ToolType . None ;
}
2022-10-03 00:03:30 +02:00
if ( activeToolType ! = firstAvailableTool | | activeTool = = null )
2020-09-11 23:46:04 +03:00
{
activeToolType = firstAvailableTool ;
RefreshActiveTool ( ) ;
}
}
}
private MapLayerFlag activeLayers ;
public MapLayerFlag ActiveLayers
{
get = > activeLayers ;
set
{
if ( activeLayers ! = value )
{
activeLayers = value ;
if ( activeTool ! = null )
{
2023-03-18 01:42:43 +01:00
activeTool . Layers = activeLayers ;
2020-09-11 23:46:04 +03:00
}
}
}
}
private ITool activeTool ;
private Form activeToolForm ;
2020-09-14 13:40:09 +03:00
// Save and re-use tool instances
private Dictionary < ToolType , IToolDialog > toolForms ;
2023-08-03 18:56:02 +02:00
private GameType oldMockGame ;
private ToolType oldSelectedTool = ToolType . None ;
private Dictionary < ToolType , Object > oldMockObjects ;
2020-09-14 18:13:57 +03:00
private ViewToolStripButton [ ] viewToolStripButtons ;
2020-09-14 13:40:09 +03:00
2020-09-11 23:46:04 +03:00
private IGamePlugin plugin ;
2022-08-10 23:59:53 +02:00
private FileType loadedFileType ;
2020-09-11 23:46:04 +03:00
private string filename ;
2023-10-18 18:05:23 +02:00
private bool startedUpdate ;
// Not sure if this lock works; multiple functions can somehow run simultaneously on the same UI update thread?
2023-03-26 18:40:14 +02:00
private readonly object jumpToBounds_lock = new Object ( ) ;
2023-03-19 00:06:21 +01:00
private bool jumpToBounds ;
2020-09-11 23:46:04 +03:00
private readonly MRU mru ;
2023-07-09 16:54:11 +02:00
private readonly UndoRedoList < UndoRedoEventArgs , ToolType > url = new UndoRedoList < UndoRedoEventArgs , ToolType > ( Globals . UndoRedoStackSize ) ;
2020-09-11 23:46:04 +03:00
private readonly Timer steamUpdateTimer = new Timer ( ) ;
2023-03-10 13:40:04 +01:00
private SimpleMultiThreading loadMultiThreader ;
private SimpleMultiThreading saveMultiThreader ;
2022-12-28 12:35:15 +01:00
public Label StatusLabel { get ; set ; }
2023-05-20 18:40:07 +02:00
private Point lastInfoPoint = new Point ( - 1 , - 1 ) ;
private Point lastInfoSubPixelPoint = new Point ( - 1 , - 1 ) ;
private String lastDescription = null ;
2022-10-02 12:40:55 +02:00
2020-09-11 23:46:04 +03:00
static MainForm ( )
{
toolTypes = ( ( IEnumerable < ToolType > ) Enum . GetValues ( typeof ( ToolType ) ) ) . Where ( t = > t ! = ToolType . None ) . ToArray ( ) ;
}
2022-07-12 07:27:13 +02:00
public MainForm ( String fileToOpen )
2020-09-11 23:46:04 +03:00
{
2022-07-12 07:27:13 +02:00
this . filename = fileToOpen ;
2020-09-11 23:46:04 +03:00
InitializeComponent ( ) ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
mapPanel . SmoothScale = Globals . MapSmoothScale ;
2023-06-21 12:18:49 +02:00
// Show on monitor that the mouse is in, since that's where the user is probably looking.
Screen s = Screen . FromPoint ( Cursor . Position ) ;
Point location = s . Bounds . Location ;
this . Left = location . X ;
this . Top = location . Y ;
2023-06-28 01:00:35 +02:00
// Synced from app settings.
this . toolsOptionsBoundsObstructFillMenuItem . Checked = Globals . BoundsObstructFill ;
this . toolsOptionsSafeDraggingMenuItem . Checked = Globals . TileDragProtect ;
this . toolsOptionsRandomizeDragPlaceMenuItem . Checked = Globals . TileDragRandomize ;
this . toolsOptionsPlacementGridMenuItem . Checked = Globals . ShowPlacementGrid ;
this . toolsOptionsOutlineAllCratesMenuItem . Checked = Globals . OutlineAllCrates ;
this . toolsOptionsCratesOnTopMenuItem . Checked = Globals . CratesOnTop ;
2022-11-09 17:40:51 +01:00
// Obey the settings.
2022-08-11 22:49:22 +02:00
this . mapPanel . SmoothScale = Globals . MapSmoothScale ;
2022-11-09 17:40:51 +01:00
this . mapPanel . BackColor = Globals . MapBackColor ;
2022-08-08 11:53:51 +02:00
SetTitle ( ) ;
2020-09-14 13:40:09 +03:00
toolForms = new Dictionary < ToolType , IToolDialog > ( ) ;
2023-08-03 18:56:02 +02:00
oldMockGame = GameType . None ;
oldMockObjects = Globals . RememberToolData ? new Dictionary < ToolType , object > ( ) : null ;
2020-09-14 18:13:57 +03:00
viewToolStripButtons = new ViewToolStripButton [ ]
{
mapToolStripButton ,
smudgeToolStripButton ,
overlayToolStripButton ,
terrainToolStripButton ,
infantryToolStripButton ,
unitToolStripButton ,
buildingToolStripButton ,
resourcesToolStripButton ,
wallsToolStripButton ,
waypointsToolStripButton ,
2022-09-19 12:23:44 +02:00
cellTriggersToolStripButton ,
selectToolStripButton ,
2020-09-14 18:13:57 +03:00
} ;
2020-09-11 23:46:04 +03:00
mru = new MRU ( "Software\\Petroglyph\\CnCRemasteredEditor" , 10 , fileRecentFilesMenuItem ) ;
mru . FileSelected + = Mru_FileSelected ;
foreach ( ToolStripButton toolStripButton in mainToolStrip . Items )
{
2023-01-10 19:58:20 +01:00
toolStripButton . MouseMove + = MainToolStrip_MouseMove ;
2020-09-11 23:46:04 +03:00
}
#if ! DEVELOPER
2022-12-28 12:35:15 +01:00
fileExportMenuItem . Enabled = false ;
2020-09-11 23:46:04 +03:00
fileExportMenuItem . Visible = false ;
developerToolStripMenuItem . Visible = false ;
#endif
2023-07-09 16:54:11 +02:00
url . Tracked + = UndoRedo_Tracked ;
2020-09-11 23:46:04 +03:00
url . Undone + = UndoRedo_Updated ;
url . Redone + = UndoRedo_Updated ;
UpdateUndoRedo ( ) ;
steamUpdateTimer . Interval = 500 ;
steamUpdateTimer . Tick + = SteamUpdateTimer_Tick ;
2023-03-10 13:40:04 +01:00
loadMultiThreader = new SimpleMultiThreading ( this ) ;
loadMultiThreader . ProcessingLabelBorder = BorderStyle . Fixed3D ;
saveMultiThreader = new SimpleMultiThreading ( this ) ;
saveMultiThreader . ProcessingLabelBorder = BorderStyle . Fixed3D ;
2020-09-11 23:46:04 +03:00
}
2022-08-08 11:53:51 +02:00
private void SetTitle ( )
{
2022-09-19 12:23:44 +02:00
const string noname = "Untitled" ;
2023-10-23 19:59:46 +02:00
String mainTitle = Program . ProgramVersionTitle ;
2023-10-18 18:05:23 +02:00
string updating = this . startedUpdate ? " [CHECKING FOR UPDATES]" : String . Empty ;
2022-09-25 12:11:59 +02:00
if ( plugin = = null )
2022-08-08 11:53:51 +02:00
{
2023-10-18 18:05:23 +02:00
this . Text = mainTitle + updating ;
2022-09-25 12:11:59 +02:00
return ;
2022-08-08 11:53:51 +02:00
}
2022-09-25 12:11:59 +02:00
string mapName = plugin . Map . BasicSection . Name ;
2023-06-18 16:56:33 +02:00
bool mapNameEmpty = plugin . MapNameIsEmpty ( mapName ) ;
bool fileNameEmpty = filename = = null ;
string mapFilename = "\"" + ( fileNameEmpty ? noname + plugin . DefaultExtension : Path . GetFileName ( filename ) ) + "\"" ;
string mapShowName ;
if ( ! mapNameEmpty & & ! fileNameEmpty )
2022-08-08 11:53:51 +02:00
{
2023-06-18 16:56:33 +02:00
mapShowName = mapFilename + " - " + mapName ;
}
else if ( ! mapNameEmpty )
{
mapShowName = mapName ;
}
else
{
mapShowName = mapFilename ;
2022-08-08 11:53:51 +02:00
}
2023-10-18 18:05:23 +02:00
this . Text = string . Format ( "{0}{1} [{2}] - {3}{4}" , mainTitle , updating , plugin . Name , mapShowName , plugin ! = null & & plugin . Dirty ? " *" : String . Empty ) ;
2022-08-08 11:53:51 +02:00
}
2020-09-11 23:46:04 +03:00
private void SteamUpdateTimer_Tick ( object sender , EventArgs e )
{
if ( SteamworksUGC . IsInit )
{
SteamworksUGC . Service ( ) ;
}
}
protected override bool ProcessCmdKey ( ref Message msg , Keys keyData )
{
2022-09-19 12:23:44 +02:00
if ( ( keyData & ( Keys . Shift | Keys . Control | Keys . Alt ) ) = = Keys . None )
2020-09-11 23:46:04 +03:00
{
2022-10-03 00:03:30 +02:00
// Evaluates the scan codes directly, so this will automatically turn into a, z, e, r, t, y, etc on an azerty keyboard.
switch ( Keyboard . GetScanCode ( msg ) )
2022-09-19 12:23:44 +02:00
{
case OemScanCode . Q :
mapToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . W :
smudgeToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . E :
overlayToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . R :
terrainToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . T :
infantryToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . Y :
unitToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . A :
buildingToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . S :
resourcesToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . D :
wallsToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . F :
waypointsToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . G :
cellTriggersToolStripButton . PerformClick ( ) ;
return true ;
case OemScanCode . H :
2023-07-15 13:54:07 +02:00
//selectToolStripButton.PerformClick();
2022-09-19 12:23:44 +02:00
return true ;
2023-06-28 01:00:35 +02:00
case OemScanCode . NumPadAsterisk :
viewZoomResetMenuItem . PerformClick ( ) ;
return true ;
2022-09-19 12:23:44 +02:00
}
2022-10-03 00:03:30 +02:00
// Map navigation shortcuts (zoom and move the camera around)
2022-10-02 13:35:27 +02:00
if ( plugin ! = null & & mapPanel . MapImage ! = null & & activeTool ! = null )
{
Point delta = Point . Empty ;
switch ( keyData )
{
case Keys . Up :
delta . Y - = 1 ;
break ;
case Keys . Down :
delta . Y + = 1 ;
break ;
case Keys . Left :
delta . X - = 1 ;
break ;
case Keys . Right :
delta . X + = 1 ;
break ;
2022-10-03 00:03:30 +02:00
case Keys . Oemplus :
case Keys . Add :
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomIn ( ) ;
2022-10-03 00:03:30 +02:00
return true ;
case Keys . OemMinus :
case Keys . Subtract :
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomOut ( ) ;
2022-10-03 00:03:30 +02:00
return true ;
2022-10-02 13:35:27 +02:00
}
if ( delta ! = Point . Empty )
{
Point curPoint = mapPanel . AutoScrollPosition ;
SizeF zoomedCell = activeTool . NavigationWidget . ZoomedCellSize ;
2023-07-08 15:08:57 +02:00
// autoscrollposition is WEIRD. Exposed as negative, needs to be given as positive.
2023-06-21 12:18:49 +02:00
mapPanel . AutoScrollPosition = new Point ( - curPoint . X + ( int ) Math . Round ( delta . X * zoomedCell . Width ) , - curPoint . Y + ( int ) Math . Round ( delta . Y * zoomedCell . Width ) ) ;
2023-06-23 02:35:01 +02:00
mapPanel . InvalidateScroll ( ) ;
2023-07-15 13:54:07 +02:00
// Map moved without mouse movement. Pretend mouse moved.
2023-06-23 02:35:01 +02:00
activeTool . NavigationWidget . Refresh ( ) ;
UpdateCellStatusLabel ( true ) ;
2022-10-03 00:03:30 +02:00
return true ;
2022-10-02 13:35:27 +02:00
}
}
2020-09-11 23:46:04 +03:00
}
else if ( keyData = = ( Keys . Control | Keys . Z ) )
{
if ( editUndoMenuItem . Enabled )
{
2022-09-08 09:55:29 +02:00
EditUndoMenuItem_Click ( this , new EventArgs ( ) ) ;
2020-09-11 23:46:04 +03:00
}
return true ;
}
else if ( keyData = = ( Keys . Control | Keys . Y ) )
{
if ( editRedoMenuItem . Enabled )
{
2022-09-08 09:55:29 +02:00
EditRedoMenuItem_Click ( this , new EventArgs ( ) ) ;
2020-09-11 23:46:04 +03:00
}
return true ;
}
return base . ProcessCmdKey ( ref msg , keyData ) ;
}
2023-07-20 01:07:00 +02:00
private void MainForm_KeyPress ( Object sender , KeyPressEventArgs e )
{
// Workaround for localised non-numpad versions of keys.
char typedChar = e . KeyChar ;
bool handled = true ;
switch ( typedChar )
{
case '*' :
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomReset ( ) ;
2023-07-20 01:07:00 +02:00
break ;
case '+' :
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomIn ( ) ;
2023-07-20 01:07:00 +02:00
break ;
case '-' :
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomOut ( ) ;
2023-07-20 01:07:00 +02:00
break ;
default :
handled = false ;
break ;
}
if ( handled )
{
e . Handled = true ;
}
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
private void ZoomIn ( )
{
if ( activeTool = = null | | activeTool . NavigationWidget . IsDragging ( ) )
{
return ;
}
mapPanel . IncreaseZoomStep ( ) ;
}
private void ZoomOut ( )
{
if ( activeTool = = null | | activeTool . NavigationWidget . IsDragging ( ) )
{
return ;
}
2023-10-13 12:55:22 +02:00
mapPanel . DecreaseZoomStep ( ) ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
}
private void ZoomReset ( )
{
mapPanel . Zoom = 1.0 ;
}
2020-09-11 23:46:04 +03:00
private void UpdateUndoRedo ( )
{
editUndoMenuItem . Enabled = url . CanUndo ;
editRedoMenuItem . Enabled = url . CanRedo ;
2022-09-08 09:55:29 +02:00
editClearUndoRedoMenuItem . Enabled = url . CanUndo | | url . CanRedo ;
2023-07-15 13:54:07 +02:00
// Some action has occurred; probably something was placed or removed. Force-refresh current cell.
2023-05-20 18:40:07 +02:00
UpdateCellStatusLabel ( true ) ;
2020-09-11 23:46:04 +03:00
}
2023-05-20 18:40:07 +02:00
private void UpdateCellStatusLabel ( bool force )
{
if ( plugin = = null | | activeTool = = null | | activeTool . NavigationWidget = = null )
{
return ;
}
Point location = activeTool . NavigationWidget . ActualMouseCell ;
2023-06-23 02:35:01 +02:00
Point subPixel = activeTool . NavigationWidget . MouseSubPixel ;
if ( force )
{
activeTool . NavigationWidget . GetMouseCellPosition ( location , out subPixel ) ;
}
2023-05-20 18:40:07 +02:00
if ( force | | location ! = lastInfoPoint | | subPixel ! = lastInfoSubPixelPoint )
{
String description = plugin . Map . GetCellDescription ( location , subPixel ) ;
if ( force | | lastDescription ! = description )
{
lastInfoPoint = location ;
lastInfoSubPixelPoint = subPixel ;
lastDescription = description ;
cellStatusLabel . Text = description ;
}
}
}
2023-07-09 16:54:11 +02:00
private void UndoRedo_Tracked ( object sender , EventArgs e )
{
UpdateUndoRedo ( ) ;
}
2023-05-20 18:40:07 +02:00
2023-07-09 16:54:11 +02:00
private void UndoRedo_Updated ( object sender , UndoRedoEventArgs e )
2020-09-11 23:46:04 +03:00
{
UpdateUndoRedo ( ) ;
}
2022-10-03 00:03:30 +02:00
#region listeners
protected override void OnLoad ( EventArgs e )
{
base . OnLoad ( e ) ;
RefreshUI ( ) ;
UpdateVisibleLayers ( ) ;
steamUpdateTimer . Start ( ) ;
}
protected override void OnClosed ( EventArgs e )
{
base . OnClosed ( e ) ;
steamUpdateTimer . Stop ( ) ;
steamUpdateTimer . Dispose ( ) ;
}
2022-09-08 09:55:29 +02:00
private void FileNewMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
2023-03-10 13:40:04 +01:00
NewFileAsk ( false , null , false ) ;
2023-01-10 19:58:20 +01:00
}
private void FileNewFromImageMenuItem_Click ( object sender , EventArgs e )
{
2023-03-10 13:40:04 +01:00
NewFileAsk ( true , null , false ) ;
2020-09-11 23:46:04 +03:00
}
2022-09-08 09:55:29 +02:00
private void FileOpenMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
2023-03-10 13:40:04 +01:00
PromptSaveMap ( OpenFile , false ) ;
}
2020-09-11 23:46:04 +03:00
2023-03-10 13:40:04 +01:00
private void OpenFile ( )
{
2023-03-10 15:09:07 +01:00
// Always remove the label when showing an Open File dialog.
SimpleMultiThreading . RemoveBusyLabel ( this ) ;
2023-07-10 17:22:34 +02:00
List < string > filters = new List < string > ( ) ;
filters . Add ( "All supported types (*.ini;*.bin;*.mpr;*.pgm)|*.ini;*.bin;*.mpr;*.pgm" ) ;
filters . Add ( TiberianDawn . GamePluginTD . FileFilter ) ;
filters . Add ( RedAlert . GamePluginRA . FileFilter ) ;
filters . Add ( "PGM files (*.pgm)|*.pgm" ) ;
filters . Add ( "All files (*.*)|*.*" ) ;
string selectedFileName = null ;
2022-09-01 13:05:49 +02:00
using ( OpenFileDialog ofd = new OpenFileDialog ( ) )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
ofd . AutoUpgradeEnabled = false ;
ofd . RestoreDirectory = true ;
2023-07-10 17:22:34 +02:00
ofd . Filter = String . Join ( "|" , filters ) ;
2023-10-18 18:05:23 +02:00
bool classicLogic = Globals . UseClassicFiles & & Globals . ClassicNoRemasterLogic ;
2023-07-10 17:22:34 +02:00
string lastFolder = mru . Files . Select ( f = > f . DirectoryName ) . Where ( d = > Directory . Exists ( d ) ) . FirstOrDefault ( ) ;
2022-09-01 13:05:49 +02:00
if ( plugin ! = null )
2020-09-11 23:46:04 +03:00
{
2023-07-10 17:22:34 +02:00
string openFolder = Path . GetDirectoryName ( filename ) ;
string constFolder = Directory . Exists ( plugin . DefaultSaveDirectory ) ? plugin . DefaultSaveDirectory : Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ;
ofd . InitialDirectory = openFolder ? ? lastFolder ? ? ( classicLogic ? Program . ApplicationPath : constFolder ) ;
2022-09-01 13:05:49 +02:00
}
else
{
2023-07-10 17:22:34 +02:00
ofd . InitialDirectory = lastFolder ? ? ( classicLogic ? Program . ApplicationPath : Globals . RootSaveDirectory ) ;
2022-09-01 13:05:49 +02:00
}
if ( ofd . ShowDialog ( ) = = DialogResult . OK )
{
selectedFileName = ofd . FileName ;
2020-09-11 23:46:04 +03:00
}
}
2022-09-01 13:05:49 +02:00
if ( selectedFileName ! = null )
2020-09-11 23:46:04 +03:00
{
2023-03-10 13:40:04 +01:00
OpenFile ( selectedFileName ) ;
2020-09-11 23:46:04 +03:00
}
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
private void FileOpenFromMixMenuItem_Click ( object sender , EventArgs e )
{
PromptSaveMap ( OpenFileFromMix , false ) ;
}
private void OpenFileFromMix ( )
{
// TODO make mix browsing ui
}
2022-09-08 09:55:29 +02:00
private void FileSaveMenuItem_Click ( object sender , EventArgs e )
2023-03-10 13:40:04 +01:00
{
2023-08-03 18:56:02 +02:00
SaveAction ( false , null , false , false ) ;
2023-03-10 13:40:04 +01:00
}
2023-08-03 18:56:02 +02:00
/// <summary>
/// Performs the saving action, and optionally executes another action after the save is done.
/// </summary>
/// <param name="dontResavePreview">Suppress generation of the preview image.</param>
/// <param name="afterSaveDone">Action to execute after the save is done.</param>
/// <param name="skipValidation">True to skip validation when saving.</param>
/// <param name="continueOnError">True to execute <paramref name="afterSaveDone"/> even if errors occurred.</param>
private void SaveAction ( bool dontResavePreview , Action afterSaveDone , bool skipValidation , bool continueOnError )
2020-09-11 23:46:04 +03:00
{
if ( plugin = = null )
{
2023-03-10 13:40:04 +01:00
afterSaveDone ? . Invoke ( ) ;
2020-09-11 23:46:04 +03:00
return ;
}
2022-08-16 00:15:46 +02:00
if ( string . IsNullOrEmpty ( filename ) | | ! Directory . Exists ( Path . GetDirectoryName ( filename ) ) )
2020-09-11 23:46:04 +03:00
{
2023-06-21 12:18:49 +02:00
SaveAsAction ( afterSaveDone , skipValidation ) ;
2022-10-03 00:03:30 +02:00
return ;
2020-09-11 23:46:04 +03:00
}
2023-06-21 12:18:49 +02:00
if ( ! this . DoValidate ( ) )
2020-09-11 23:46:04 +03:00
{
2023-08-03 18:56:02 +02:00
if ( continueOnError )
{
afterSaveDone ( ) ;
}
2022-10-03 00:03:30 +02:00
return ;
2020-09-11 23:46:04 +03:00
}
2022-10-03 00:03:30 +02:00
var fileInfo = new FileInfo ( filename ) ;
2023-03-10 21:45:02 +01:00
SaveChosenFile ( fileInfo . FullName , loadedFileType , dontResavePreview , afterSaveDone ) ;
2020-09-11 23:46:04 +03:00
}
2022-09-08 09:55:29 +02:00
private void FileSaveAsMenuItem_Click ( object sender , EventArgs e )
2023-03-10 13:40:04 +01:00
{
2023-06-21 12:18:49 +02:00
SaveAsAction ( null , false ) ;
2023-03-10 13:40:04 +01:00
}
2023-06-21 12:18:49 +02:00
private void SaveAsAction ( Action afterSaveDone , bool skipValidation )
2020-09-11 23:46:04 +03:00
{
if ( plugin = = null )
{
2023-03-10 13:40:04 +01:00
afterSaveDone ? . Invoke ( ) ;
2020-09-11 23:46:04 +03:00
return ;
}
2023-06-21 12:18:49 +02:00
if ( ! skipValidation & & ! this . DoValidate ( ) )
2022-08-14 15:12:30 +02:00
{
return ;
}
2022-09-01 13:05:49 +02:00
string savePath = null ;
using ( SaveFileDialog sfd = new SaveFileDialog ( ) )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
sfd . AutoUpgradeEnabled = false ;
2023-07-10 17:22:34 +02:00
sfd . RestoreDirectory = false ;
2023-10-18 18:05:23 +02:00
bool classicLogic = Globals . UseClassicFiles & & Globals . ClassicNoRemasterLogic ;
2023-07-10 17:22:34 +02:00
string lastFolder = mru . Files . Select ( f = > f . DirectoryName ) . Where ( d = > Directory . Exists ( d ) ) . FirstOrDefault ( ) ;
string openFolder = Path . GetDirectoryName ( filename ) ;
string constFolder = Directory . Exists ( plugin . DefaultSaveDirectory ) ? plugin . DefaultSaveDirectory : Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ;
2022-09-01 13:05:49 +02:00
var filters = new List < string > ( ) ;
2023-07-10 17:22:34 +02:00
filters . Add ( plugin . SaveFilter ) ;
2022-09-01 13:05:49 +02:00
filters . Add ( "All files (*.*)|*.*" ) ;
2023-07-10 17:22:34 +02:00
sfd . InitialDirectory = openFolder ? ? lastFolder ? ? ( classicLogic ? Program . ApplicationPath : constFolder ) ;
2022-09-01 13:05:49 +02:00
sfd . Filter = string . Join ( "|" , filters ) ;
if ( ! string . IsNullOrEmpty ( filename ) )
{
sfd . FileName = Path . GetFileName ( filename ) ;
}
2023-07-10 17:22:34 +02:00
else if ( ! plugin . MapNameIsEmpty ( plugin . Map . BasicSection . Name ) )
{
sfd . FileName = Path . GetFileName ( plugin . Map . BasicSection . Name ) ;
}
2022-09-01 13:05:49 +02:00
if ( sfd . ShowDialog ( this ) = = DialogResult . OK )
{
savePath = sfd . FileName ;
}
2020-09-11 23:46:04 +03:00
}
2023-03-10 13:40:04 +01:00
if ( savePath = = null )
{
afterSaveDone ? . Invoke ( ) ;
}
else
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
var fileInfo = new FileInfo ( savePath ) ;
2023-03-10 21:45:02 +01:00
SaveChosenFile ( fileInfo . FullName , FileType . INI , false , afterSaveDone ) ;
2020-09-11 23:46:04 +03:00
}
}
2023-06-21 12:18:49 +02:00
private bool DoValidate ( )
{
String errors = plugin . Validate ( true ) ;
2023-07-08 15:08:57 +02:00
if ( ! String . IsNullOrEmpty ( errors ) )
2023-06-21 12:18:49 +02:00
{
String message = errors + "\n\nContinue map save?" ;
DialogResult dr = SimpleMultiThreading . ShowMessageBoxThreadSafe ( this , message , "Warning" , MessageBoxButtons . YesNo , MessageBoxIcon . Warning , MessageBoxDefaultButton . Button2 ) ;
if ( dr = = DialogResult . No )
{
return false ;
}
}
errors = plugin . Validate ( false ) ;
if ( errors ! = null )
{
SimpleMultiThreading . ShowMessageBoxThreadSafe ( this , errors , "Validation Error" , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ;
return false ;
}
return true ;
}
2022-09-08 09:55:29 +02:00
private void FileExportMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
2022-12-28 12:35:15 +01:00
#if DEVELOPER
2020-09-11 23:46:04 +03:00
if ( plugin = = null )
{
return ;
}
2022-08-14 15:12:30 +02:00
String errors = plugin . Validate ( ) ;
if ( errors ! = null )
{
MessageBox . Show ( errors , "Validation Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
return ;
}
2022-09-01 13:05:49 +02:00
string savePath = null ;
using ( SaveFileDialog sfd = new SaveFileDialog ( ) )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
sfd . AutoUpgradeEnabled = false ;
sfd . RestoreDirectory = true ;
2022-10-02 12:40:55 +02:00
2023-04-03 21:17:00 +02:00
sfd . Filter = "PGM files (*.pgm)|*.pgm" ;
2022-09-01 13:05:49 +02:00
if ( sfd . ShowDialog ( this ) = = DialogResult . OK )
{
savePath = sfd . FileName ;
}
}
if ( savePath ! = null )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
plugin . Save ( savePath , FileType . MEG ) ;
2020-09-11 23:46:04 +03:00
}
2022-12-28 12:35:15 +01:00
#endif
2020-09-11 23:46:04 +03:00
}
2022-09-08 09:55:29 +02:00
private void FileExitMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
Close ( ) ;
}
2022-09-08 09:55:29 +02:00
private void EditUndoMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
2023-07-15 13:54:07 +02:00
if ( activeTool = = null | | activeTool . IsBusy )
{
return ;
}
2020-09-11 23:46:04 +03:00
if ( url . CanUndo )
{
2022-09-19 12:23:44 +02:00
url . Undo ( new UndoRedoEventArgs ( mapPanel , plugin ) ) ;
2020-09-11 23:46:04 +03:00
}
}
2022-09-08 09:55:29 +02:00
private void EditRedoMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
2023-07-15 13:54:07 +02:00
if ( activeTool = = null | | activeTool . IsBusy )
{
return ;
}
2020-09-11 23:46:04 +03:00
if ( url . CanRedo )
{
2022-09-19 12:23:44 +02:00
url . Redo ( new UndoRedoEventArgs ( mapPanel , plugin ) ) ;
2020-09-11 23:46:04 +03:00
}
}
2022-09-08 09:55:29 +02:00
private void EditClearUndoRedoMenuItem_Click ( object sender , EventArgs e )
{
2023-10-23 19:59:46 +02:00
if ( DialogResult . Yes = = MessageBox . Show ( "This will remove all undo/redo information. Are you sure?" , Program . ProgramVersionTitle , MessageBoxButtons . YesNo ) )
2022-09-08 09:55:29 +02:00
{
url . Clear ( ) ;
}
}
private void SettingsMapSettingsMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
if ( plugin = = null )
{
return ;
}
2023-07-06 22:48:29 +02:00
bool wasSolo = plugin . Map . BasicSection . SoloMission ;
bool wasExpanded = plugin . Map . BasicSection . ExpansionEnabled ;
2022-09-19 12:23:44 +02:00
PropertyTracker < BasicSection > basicSettings = new PropertyTracker < BasicSection > ( plugin . Map . BasicSection ) ;
PropertyTracker < BriefingSection > briefingSettings = new PropertyTracker < BriefingSection > ( plugin . Map . BriefingSection ) ;
2022-09-22 01:26:46 +02:00
PropertyTracker < SoleSurvivor . CratesSection > cratesSettings = null ;
2023-02-24 22:08:01 +01:00
if ( plugin . GameType = = GameType . SoleSurvivor & & plugin is SoleSurvivor . GamePluginSS ssPlugin )
2022-09-22 01:26:46 +02:00
{
cratesSettings = new PropertyTracker < SoleSurvivor . CratesSection > ( ssPlugin . CratesSection ) ;
}
2023-07-08 15:08:57 +02:00
string extraIniText = plugin . GetExtraIniText ( ) ;
2022-11-29 14:11:35 +01:00
if ( extraIniText . Trim ( '\r' , '\n' ) . Length = = 0 )
extraIniText = String . Empty ;
2022-09-19 12:23:44 +02:00
Dictionary < House , PropertyTracker < House > > houseSettingsTrackers = plugin . Map . Houses . ToDictionary ( h = > h , h = > new PropertyTracker < House > ( h ) ) ;
2023-07-08 15:08:57 +02:00
bool amStatusChanged = false ;
bool multiStatusChanged = false ;
bool iniTextChanged = false ;
2023-07-09 00:54:35 +02:00
bool footPrintsChanged = false ;
2022-09-22 01:26:46 +02:00
using ( MapSettingsDialog msd = new MapSettingsDialog ( plugin , basicSettings , briefingSettings , cratesSettings , houseSettingsTrackers , extraIniText ) )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
msd . StartPosition = FormStartPosition . CenterParent ;
if ( msd . ShowDialog ( this ) = = DialogResult . OK )
2020-09-11 23:46:04 +03:00
{
2022-09-19 12:23:44 +02:00
bool hasChanges = basicSettings . HasChanges | | briefingSettings . HasChanges ;
2022-09-01 13:05:49 +02:00
basicSettings . Commit ( ) ;
briefingSettings . Commit ( ) ;
2022-09-22 01:26:46 +02:00
if ( cratesSettings ! = null )
{
cratesSettings . Commit ( ) ;
}
2022-09-01 13:05:49 +02:00
foreach ( var houseSettingsTracker in houseSettingsTrackers . Values )
{
2022-09-19 12:23:44 +02:00
if ( houseSettingsTracker . HasChanges )
hasChanges = true ;
2022-09-01 13:05:49 +02:00
houseSettingsTracker . Commit ( ) ;
}
2022-11-29 14:11:35 +01:00
// Combine diacritics into their characters, and remove characters not included in DOS-437.
string normalised = ( msd . ExtraIniText ? ? String . Empty ) . Normalize ( NormalizationForm . FormC ) ;
Encoding dos437 = Encoding . GetEncoding ( 437 ) ;
// DOS chars excluding specials at the start and end. Explicitly add tab, then the normal range from 32 to 254.
2022-12-05 18:25:17 +01:00
HashSet < Char > dos437chars = ( "\t\r\n" + String . Concat ( Enumerable . Range ( 32 , 256 - 32 - 1 ) . Select ( i = > dos437 . GetString ( new Byte [ ] { ( byte ) i } ) ) ) ) . ToHashSet ( ) ;
2022-11-29 14:11:35 +01:00
normalised = new String ( normalised . Where ( ch = > dos437chars . Contains ( ch ) ) . ToArray ( ) ) ;
// Ignore trivial line changes. This will not detect any irrelevant but non-trivial changes like swapping lines, though.
String checkTextNew = Regex . Replace ( normalised , "[\\r\\n]+" , "\n" ) . Trim ( '\n' ) ;
String checkTextOrig = Regex . Replace ( extraIniText ? ? String . Empty , "[\\r\\n]+" , "\n" ) . Trim ( '\n' ) ;
2023-07-08 15:08:57 +02:00
amStatusChanged = wasExpanded ! = plugin . Map . BasicSection . ExpansionEnabled ;
multiStatusChanged = wasSolo ! = plugin . Map . BasicSection . SoloMission ;
iniTextChanged = ! checkTextOrig . Equals ( checkTextNew , StringComparison . OrdinalIgnoreCase ) ;
2023-07-06 22:48:29 +02:00
// All three of those warrant a rules reset.
// TODO: give warning on the multiplay rules changes.
if ( amStatusChanged | | multiStatusChanged | | iniTextChanged )
2022-09-10 02:47:12 +02:00
{
2023-07-09 00:54:35 +02:00
IEnumerable < string > errors = plugin . SetExtraIniText ( normalised , out footPrintsChanged ) ;
2023-07-08 15:08:57 +02:00
if ( errors ! = null & & errors . Count ( ) > 0 )
2022-10-28 15:15:52 +02:00
{
2023-04-21 18:53:12 +02:00
using ( ErrorMessageBox emb = new ErrorMessageBox ( ) )
{
2023-10-23 19:59:46 +02:00
emb . Title = Program . ProgramVersionTitle ;
2023-04-21 18:53:12 +02:00
emb . Message = "Errors occurred when applying rule changes:" ;
2023-07-08 15:08:57 +02:00
emb . Errors = errors ;
2023-04-21 18:53:12 +02:00
emb . StartPosition = FormStartPosition . CenterParent ;
emb . ShowDialog ( this ) ;
}
2022-10-28 15:15:52 +02:00
}
2023-07-06 22:48:29 +02:00
// Maybe make more advanced logic to check if any bibs changed, and don't clear if not needed?
2022-09-19 12:23:44 +02:00
hasChanges = true ;
2022-09-10 02:47:12 +02:00
}
2022-09-19 12:23:44 +02:00
plugin . Dirty = hasChanges ;
2020-09-11 23:46:04 +03:00
}
}
2023-07-09 00:54:35 +02:00
if ( footPrintsChanged | | amStatusChanged )
2022-09-08 09:55:29 +02:00
{
2022-09-10 02:47:12 +02:00
// If Aftermath units were disabled, we can't guarantee none of them are still in
2022-09-08 09:55:29 +02:00
// the undo/redo history, so the undo/redo history is cleared to avoid issues.
// The rest of the cleanup can be found in the ViewTool class, in the BasicSection_PropertyChanged function.
2022-09-10 02:47:12 +02:00
// Rule changes will clear undo to avoid conflicts with placed smudge types.
2022-09-08 09:55:29 +02:00
url . Clear ( ) ;
}
2020-09-11 23:46:04 +03:00
}
2022-09-08 09:55:29 +02:00
private void SettingsTeamTypesMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
if ( plugin = = null )
{
return ;
}
int maxTeams = 0 ;
switch ( plugin . GameType )
{
case GameType . TiberianDawn :
2022-09-22 01:26:46 +02:00
case GameType . SoleSurvivor :
maxTeams = TiberianDawn . Constants . MaxTeams ;
2020-09-11 23:46:04 +03:00
break ;
case GameType . RedAlert :
2022-09-22 01:26:46 +02:00
maxTeams = RedAlert . Constants . MaxTeams ;
2020-09-11 23:46:04 +03:00
break ;
}
2022-09-01 13:05:49 +02:00
using ( TeamTypesDialog ttd = new TeamTypesDialog ( plugin , maxTeams ) )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
ttd . StartPosition = FormStartPosition . CenterParent ;
if ( ttd . ShowDialog ( this ) = = DialogResult . OK )
{
2022-10-28 15:15:52 +02:00
List < TeamType > oldTeamTypes = plugin . Map . TeamTypes . ToList ( ) ;
// Clone of old triggers
List < Trigger > oldTriggers = plugin . Map . Triggers . Select ( tr = > tr . Clone ( ) ) . ToList ( ) ;
2022-09-01 13:05:49 +02:00
plugin . Map . TeamTypes . Clear ( ) ;
2022-10-15 00:58:47 +02:00
plugin . Map . ApplyTeamTypeRenames ( ttd . RenameActions ) ;
2023-07-09 16:54:11 +02:00
// Triggers in their new state after the teamtype item renames.
2022-10-28 15:15:52 +02:00
List < Trigger > newTriggers = plugin . Map . Triggers . Select ( tr = > tr . Clone ( ) ) . ToList ( ) ;
2022-09-01 13:05:49 +02:00
plugin . Map . TeamTypes . AddRange ( ttd . TeamTypes . OrderBy ( t = > t . Name , new ExplorerComparer ( ) ) . Select ( t = > t . Clone ( ) ) ) ;
2022-10-28 15:15:52 +02:00
List < TeamType > newTeamTypes = plugin . Map . TeamTypes . ToList ( ) ;
bool origDirtyState = plugin . Dirty ;
void undoAction ( UndoRedoEventArgs ev )
{
DialogResult dr = MessageBox . Show ( this , "This will undo all teamtype editing actions you performed. Are you sure you want to continue?" ,
"Warning" , MessageBoxButtons . YesNo , MessageBoxIcon . Warning ) ;
if ( dr = = DialogResult . No )
{
ev . Cancelled = true ;
return ;
}
if ( ev . Plugin ! = null )
{
ev . Map . Triggers = oldTriggers ;
2023-03-18 01:42:43 +01:00
ev . Map . TeamTypes . Clear ( ) ;
ev . Map . TeamTypes . AddRange ( oldTeamTypes ) ;
2022-10-28 15:15:52 +02:00
ev . Plugin . Dirty = origDirtyState ;
}
}
void redoAction ( UndoRedoEventArgs ev )
{
DialogResult dr = MessageBox . Show ( this , "This will redo all teamtype editing actions you undid. Are you sure you want to continue?" ,
"Warning" , MessageBoxButtons . YesNo , MessageBoxIcon . Warning ) ;
if ( dr = = DialogResult . No )
{
ev . Cancelled = true ;
return ;
}
if ( ev . Plugin ! = null )
{
2023-03-18 01:42:43 +01:00
ev . Map . TeamTypes . Clear ( ) ;
ev . Map . TeamTypes . AddRange ( newTeamTypes ) ;
2022-10-28 15:15:52 +02:00
ev . Map . Triggers = newTriggers ;
ev . Plugin . Dirty = true ;
}
}
2023-07-09 16:54:11 +02:00
url . Track ( undoAction , redoAction , ToolType . None ) ;
2022-09-01 13:05:49 +02:00
plugin . Dirty = true ;
}
2020-09-11 23:46:04 +03:00
}
}
2022-09-08 09:55:29 +02:00
private void SettingsTriggersMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
if ( plugin = = null )
{
return ;
}
int maxTriggers = 0 ;
switch ( plugin . GameType )
{
case GameType . TiberianDawn :
2022-09-22 01:26:46 +02:00
case GameType . SoleSurvivor :
maxTriggers = TiberianDawn . Constants . MaxTriggers ;
2020-09-11 23:46:04 +03:00
break ;
case GameType . RedAlert :
2022-09-22 01:26:46 +02:00
maxTriggers = RedAlert . Constants . MaxTriggers ;
2020-09-11 23:46:04 +03:00
break ;
}
2022-09-01 13:05:49 +02:00
using ( TriggersDialog td = new TriggersDialog ( plugin , maxTriggers ) )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
td . StartPosition = FormStartPosition . CenterParent ;
if ( td . ShowDialog ( this ) = = DialogResult . OK )
2020-09-11 23:46:04 +03:00
{
2022-10-28 15:15:52 +02:00
List < Trigger > newTriggers = td . Triggers . OrderBy ( t = > t . Name , new ExplorerComparer ( ) ) . ToList ( ) ;
if ( Trigger . CheckForChanges ( plugin . Map . Triggers . ToList ( ) , newTriggers ) )
2022-09-01 13:05:49 +02:00
{
2022-10-28 15:15:52 +02:00
bool origDirtyState = plugin . Dirty ;
Dictionary < object , string > undoList ;
Dictionary < object , string > redoList ;
Dictionary < CellTrigger , int > cellTriggerLocations ;
// Applies all the rename actions, and returns lists of actual changes. Also cleans up objects that are now linked
// to incorrect triggers. This action may modify the triggers in the 'newTriggers' list to clean up inconsistencies.
plugin . Map . ApplyTriggerChanges ( td . RenameActions , out undoList , out redoList , out cellTriggerLocations , newTriggers ) ;
// New triggers are cloned, so these are safe to take as backup.
List < Trigger > oldTriggers = plugin . Map . Triggers . ToList ( ) ;
// This will notify tool windows to update their trigger lists.
plugin . Map . Triggers = newTriggers ;
2022-09-01 13:05:49 +02:00
plugin . Dirty = true ;
2022-10-28 15:15:52 +02:00
void undoAction ( UndoRedoEventArgs ev )
{
DialogResult dr = MessageBox . Show ( this , "This will undo all trigger editing actions you performed. Are you sure you want to continue?" ,
"Warning" , MessageBoxButtons . YesNo , MessageBoxIcon . Warning ) ;
if ( dr = = DialogResult . No )
{
ev . Cancelled = true ;
return ;
}
foreach ( Object obj in undoList . Keys )
{
if ( obj is ITechno techno )
{
techno . Trigger = undoList [ obj ] ;
}
else if ( obj is TeamType teamType )
{
teamType . Trigger = undoList [ obj ] ;
}
else if ( obj is CellTrigger celltrigger )
{
celltrigger . Trigger = undoList [ obj ] ;
// In case it's removed, restore.
2023-03-18 01:42:43 +01:00
if ( ev . Map ! = null )
{
ev . Map . CellTriggers [ cellTriggerLocations [ celltrigger ] ] = celltrigger ;
}
2022-10-28 15:15:52 +02:00
}
}
if ( ev . Plugin ! = null )
{
2023-03-18 01:42:43 +01:00
ev . Map . Triggers = oldTriggers ;
2022-10-28 15:15:52 +02:00
ev . Plugin . Dirty = origDirtyState ;
}
// Repaint map labels
2023-03-18 01:42:43 +01:00
ev . MapPanel ? . Invalidate ( ) ;
2022-10-28 15:15:52 +02:00
}
void redoAction ( UndoRedoEventArgs ev )
{
DialogResult dr = MessageBox . Show ( this , "This will redo all trigger editing actions you undid. Are you sure you want to continue?" ,
"Warning" , MessageBoxButtons . YesNo , MessageBoxIcon . Warning ) ;
if ( dr = = DialogResult . No )
{
ev . Cancelled = true ;
return ;
}
foreach ( Object obj in redoList . Keys )
{
if ( obj is ITechno techno )
{
techno . Trigger = redoList [ obj ] ;
}
else if ( obj is TeamType teamType )
{
teamType . Trigger = redoList [ obj ] ;
}
else if ( obj is CellTrigger celltrigger )
{
celltrigger . Trigger = redoList [ obj ] ;
2023-03-18 01:42:43 +01:00
if ( Trigger . IsEmpty ( celltrigger . Trigger ) & & ev . Map ! = null )
2022-10-28 15:15:52 +02:00
{
ev . Map . CellTriggers [ cellTriggerLocations [ celltrigger ] ] = null ;
}
}
}
if ( ev . Plugin ! = null )
{
2023-03-18 01:42:43 +01:00
ev . Map . Triggers = newTriggers ;
2022-10-28 15:15:52 +02:00
ev . Plugin . Dirty = true ;
}
// Repaint map labels
2023-03-18 01:42:43 +01:00
ev . MapPanel ? . Invalidate ( ) ;
2022-10-28 15:15:52 +02:00
}
2023-07-15 13:54:07 +02:00
// These changes can affect a whole lot of tools.
url . Track ( undoAction , redoAction , ToolType . Terrain | ToolType . Infantry | ToolType . Unit | ToolType . Building | ToolType . CellTrigger ) ;
2022-11-18 15:18:52 +01:00
// No longer a full refresh, since celltriggers function is no longer disabled when no triggers are found.
2022-10-28 15:15:52 +02:00
mapPanel . Invalidate ( ) ;
2022-09-01 13:05:49 +02:00
}
}
}
}
2023-03-01 17:27:21 +01:00
private void ToolsOptionsBoundsObstructFillMenuItem_CheckedChanged ( Object sender , EventArgs e )
2023-02-28 22:50:23 +01:00
{
if ( sender is ToolStripMenuItem tsmi )
{
Globals . BoundsObstructFill = tsmi . Checked ;
}
}
2023-02-24 22:08:01 +01:00
private void ToolsOptionsSafeDraggingMenuItem_CheckedChanged ( Object sender , EventArgs e )
2023-01-10 19:58:20 +01:00
{
if ( sender is ToolStripMenuItem tsmi )
{
Globals . TileDragProtect = tsmi . Checked ;
}
}
2023-03-01 17:27:21 +01:00
private void ToolsOptionsRandomizeDragPlaceMenuItem_CheckedChanged ( Object sender , EventArgs e )
{
if ( sender is ToolStripMenuItem tsmi )
{
Globals . TileDragRandomize = tsmi . Checked ;
}
}
2023-02-24 22:08:01 +01:00
private void ToolsOptionsPlacementGridMenuItem_CheckedChanged ( Object sender , EventArgs e )
{
if ( sender is ToolStripMenuItem tsmi )
{
Globals . ShowPlacementGrid = tsmi . Checked ;
}
}
2023-03-01 17:27:21 +01:00
private void ToolsOptionsCratesOnTopMenuItem_CheckedChanged ( Object sender , EventArgs e )
{
if ( sender is ToolStripMenuItem tsmi )
{
Globals . CratesOnTop = tsmi . Checked ;
}
if ( plugin ! = null )
{
Map map = plugin . Map ;
CellMetrics cm = map . Metrics ;
mapPanel . Invalidate ( map , map . Overlay . Select ( ov = > cm . GetLocation ( ov . Cell ) ) . Where ( c = > c . HasValue ) . Cast < Point > ( ) ) ;
}
}
2023-03-19 00:06:21 +01:00
private void toolsOptionsOutlineAllCratesMenuItem_Click ( Object sender , EventArgs e )
{
if ( sender is ToolStripMenuItem tsmi )
{
Globals . OutlineAllCrates = tsmi . Checked ;
}
mapPanel . Invalidate ( ) ;
}
2022-10-13 18:45:23 +02:00
private void ToolsStatsGameObjectsMenuItem_Click ( Object sender , EventArgs e )
{
if ( plugin = = null )
{
return ;
}
using ( ErrorMessageBox emb = new ErrorMessageBox ( ) )
{
emb . Title = "Map objects" ;
emb . Message = "Map objects overview:" ;
emb . Errors = plugin . AssessMapItems ( ) ;
emb . StartPosition = FormStartPosition . CenterParent ;
emb . ShowDialog ( this ) ;
}
}
private void ToolsStatsPowerMenuItem_Click ( Object sender , EventArgs e )
2022-09-01 13:05:49 +02:00
{
if ( plugin = = null )
{
return ;
}
using ( ErrorMessageBox emb = new ErrorMessageBox ( ) )
{
emb . Title = "Power usage" ;
emb . Message = "Power balance per House:" ;
2022-10-18 08:56:25 +02:00
emb . Errors = plugin . Map . AssessPower ( plugin . GetHousesWithProduction ( ) ) ;
2022-09-01 13:05:49 +02:00
emb . StartPosition = FormStartPosition . CenterParent ;
emb . ShowDialog ( this ) ;
}
}
2022-10-13 18:45:23 +02:00
private void ToolsStatsStorageMenuItem_Click ( Object sender , EventArgs e )
2022-09-10 02:47:12 +02:00
{
if ( plugin = = null )
{
return ;
}
using ( ErrorMessageBox emb = new ErrorMessageBox ( ) )
{
emb . Title = "Silo storage" ;
emb . Message = "Available silo storage per House:" ;
2022-10-18 08:56:25 +02:00
emb . Errors = plugin . Map . AssessStorage ( plugin . GetHousesWithProduction ( ) ) ;
2022-09-10 02:47:12 +02:00
emb . StartPosition = FormStartPosition . CenterParent ;
emb . ShowDialog ( this ) ;
}
}
2022-09-19 12:23:44 +02:00
private void ToolsRandomizeTilesMenuItem_Click ( Object sender , EventArgs e )
{
if ( plugin ! = null )
{
String feedback = TemplateTool . RandomizeTiles ( plugin , mapPanel , url ) ;
2023-10-23 19:59:46 +02:00
MessageBox . Show ( feedback , Program . ProgramVersionTitle ) ;
2022-09-19 12:23:44 +02:00
}
}
2022-09-08 09:55:29 +02:00
private void ToolsExportImage_Click ( Object sender , EventArgs e )
2022-09-01 13:05:49 +02:00
{
if ( plugin = = null )
{
return ;
}
2023-07-10 17:22:34 +02:00
string lastFolder = mru . Files . Select ( f = > f . DirectoryName ) . Where ( d = > Directory . Exists ( d ) ) . FirstOrDefault ( ) ;
using ( ImageExportDialog imex = new ImageExportDialog ( plugin , activeLayers , filename , lastFolder ) )
2022-09-01 13:05:49 +02:00
{
2022-10-17 18:43:34 +02:00
imex . StartPosition = FormStartPosition . CenterParent ;
2022-09-30 14:52:52 +02:00
imex . ShowDialog ( this ) ;
2020-09-11 23:46:04 +03:00
}
}
2023-06-28 01:00:35 +02:00
private void ViewZoomInMenuItem_Click ( Object sender , EventArgs e )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomIn ( ) ;
2023-06-28 01:00:35 +02:00
}
private void ViewZoomOutMenuItem_Click ( Object sender , EventArgs e )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomOut ( ) ;
2023-06-28 01:00:35 +02:00
}
private void ViewZoomResetMenuItem_Click ( Object sender , EventArgs e )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomReset ( ) ;
2023-06-28 01:00:35 +02:00
}
private void ViewZoomBoundsMenuItem_Click ( Object sender , EventArgs e )
{
lock ( jumpToBounds_lock )
{
this . jumpToBounds = true ;
}
mapPanel . Refresh ( ) ;
}
2020-09-11 23:46:04 +03:00
private void Mru_FileSelected ( object sender , FileInfo e )
{
2023-03-10 13:40:04 +01:00
if ( File . Exists ( e . FullName ) )
{
2023-06-21 12:18:49 +02:00
OpenFileAsk ( e . FullName ) ;
2023-03-10 13:40:04 +01:00
}
else
{
MessageBox . Show ( string . Format ( "Error loading {0}: the file was not found." , e . Name ) , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
mru . Remove ( e ) ;
}
2020-09-11 23:46:04 +03:00
}
2023-07-15 13:54:07 +02:00
private void ViewTool_RequestMouseInfoRefresh ( object sender , EventArgs e )
{
// Viewtool has asked a deliberate refresh; probably the map position jumped without the mouse moving.
UpdateCellStatusLabel ( true ) ;
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
2022-09-08 09:55:29 +02:00
private void MapPanel_MouseMove ( object sender , MouseEventArgs e )
2020-09-11 23:46:04 +03:00
{
2023-05-20 18:40:07 +02:00
UpdateCellStatusLabel ( false ) ;
2020-09-11 23:46:04 +03:00
}
2023-05-20 18:40:07 +02:00
2023-03-10 13:40:04 +01:00
#endregion
2022-10-03 00:03:30 +02:00
2023-03-10 13:40:04 +01:00
#region Additional logic for listeners
2020-09-11 23:46:04 +03:00
2023-03-10 13:40:04 +01:00
private void NewFileAsk ( bool withImage , string imagePath , bool skipPrompt )
2022-10-02 12:40:55 +02:00
{
2023-03-10 13:40:04 +01:00
if ( skipPrompt )
2022-10-02 12:40:55 +02:00
{
2023-03-10 13:40:04 +01:00
NewFile ( withImage , imagePath ) ;
2022-10-02 12:40:55 +02:00
}
2023-03-10 13:40:04 +01:00
else {
PromptSaveMap ( ( ) = > NewFile ( withImage , imagePath ) , false ) ;
}
}
private void NewFile ( bool withImage , string imagePath )
{
2022-10-02 12:40:55 +02:00
GameType gameType = GameType . None ;
string theater = null ;
bool isTdMegaMap = false ;
2023-07-27 16:38:56 +02:00
bool isSinglePlay = false ;
2023-02-24 22:08:01 +01:00
using ( NewMapDialog nmd = new NewMapDialog ( withImage ) )
2022-10-02 12:40:55 +02:00
{
2023-02-26 22:05:24 +01:00
nmd . StartPosition = FormStartPosition . CenterParent ;
if ( nmd . ShowDialog ( this ) ! = DialogResult . OK )
2022-10-02 12:40:55 +02:00
{
return ;
}
gameType = nmd . GameType ;
isTdMegaMap = nmd . MegaMap ;
2023-07-27 16:38:56 +02:00
isSinglePlay = nmd . SinglePlayer ;
theater = nmd . Theater ;
2022-10-02 12:40:55 +02:00
}
2023-02-24 22:08:01 +01:00
if ( withImage & & imagePath = = null )
2023-01-10 19:58:20 +01:00
{
using ( OpenFileDialog ofd = new OpenFileDialog ( ) )
{
ofd . AutoUpgradeEnabled = false ;
ofd . RestoreDirectory = true ;
ofd . Filter = "Image Files (*.png, *.bmp, *.gif)|*.png;*.bmp;*.gif|All Files (*.*)|*.*" ;
if ( ofd . ShowDialog ( ) ! = DialogResult . OK )
{
return ;
}
imagePath = ofd . FileName ;
}
}
2022-10-02 12:40:55 +02:00
Unload ( ) ;
2023-01-10 19:58:20 +01:00
String loading = "Loading new map" ;
if ( withImage )
loading + = " from image" ;
2023-03-10 13:40:04 +01:00
loadMultiThreader . ExecuteThreaded (
2023-07-27 16:38:56 +02:00
( ) = > NewFile ( gameType , imagePath , theater , isTdMegaMap , isSinglePlay , this ) ,
2023-03-10 13:40:04 +01:00
PostLoad , true ,
( e , l ) = > LoadUnloadUi ( e , l , loadMultiThreader ) ,
loading ) ;
2022-10-02 12:40:55 +02:00
}
2023-06-21 12:18:49 +02:00
private void OpenFileAsk ( String fileName )
2022-07-12 07:27:13 +02:00
{
2023-06-21 12:18:49 +02:00
PromptSaveMap ( ( ) = > OpenFile ( fileName ) , false ) ;
2023-03-10 13:40:04 +01:00
}
2023-06-21 12:18:49 +02:00
2023-03-10 13:40:04 +01:00
private void OpenFile ( String fileName )
{
2022-07-12 07:27:13 +02:00
var fileInfo = new FileInfo ( fileName ) ;
2022-10-02 12:40:55 +02:00
String name = fileInfo . FullName ;
2023-07-06 22:48:29 +02:00
if ( ! IdentifyMap ( name , out FileType fileType , out GameType gameType , out bool isMegaMap , out string theater ) )
2022-10-02 12:40:55 +02:00
{
2023-02-24 22:08:01 +01:00
string extension = Path . GetExtension ( name ) . TrimStart ( '.' ) ;
2023-02-26 22:05:24 +01:00
// No point in supporting jpeg here; the mapping needs distinct colours without fades.
2023-02-24 22:08:01 +01:00
if ( "PNG" . Equals ( extension , StringComparison . OrdinalIgnoreCase )
| | "BMP" . Equals ( extension , StringComparison . OrdinalIgnoreCase )
| | "GIF" . Equals ( extension , StringComparison . OrdinalIgnoreCase )
| | "TIF" . Equals ( extension , StringComparison . OrdinalIgnoreCase )
| | "TIFF" . Equals ( extension , StringComparison . OrdinalIgnoreCase ) )
{
try
{
using ( Bitmap bm = new Bitmap ( name ) )
{
// Don't need to do anything except open this to confirm it's supported
}
2023-03-10 13:40:04 +01:00
NewFileAsk ( true , name , true ) ;
2023-02-24 22:08:01 +01:00
return ;
}
catch
{
// Ignore and just fall through.
}
}
2022-10-28 15:15:52 +02:00
MessageBox . Show ( string . Format ( "Error loading {0}: {1}" , fileInfo . Name , "Could not identify map type." ) , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
2022-10-02 12:40:55 +02:00
return ;
}
2023-03-10 13:40:04 +01:00
loadMultiThreader . ExecuteThreaded (
2023-07-06 22:48:29 +02:00
( ) = > LoadFile ( name , fileType , gameType , theater , isMegaMap ) ,
2023-03-10 13:40:04 +01:00
PostLoad , true ,
( e , l ) = > LoadUnloadUi ( e , l , loadMultiThreader ) ,
"Loading map" ) ;
2022-10-02 12:40:55 +02:00
}
2023-03-10 21:45:02 +01:00
private void SaveChosenFile ( string saveFilename , FileType inputNameType , bool dontResavePreview , Action afterSaveDone )
2022-10-03 00:03:30 +02:00
{
// This part assumes validation is already done.
FileType fileType = FileType . None ;
switch ( Path . GetExtension ( saveFilename ) . ToLower ( ) )
{
case ".ini" :
case ".mpr" :
fileType = FileType . INI ;
break ;
case ".bin" :
fileType = FileType . BIN ;
break ;
}
if ( fileType = = FileType . None )
{
if ( inputNameType ! = FileType . None )
{
fileType = inputNameType ;
}
else
{
// Just default to ini
fileType = FileType . INI ;
}
}
2023-07-06 22:48:29 +02:00
if ( plugin . MapNameIsEmpty ( plugin . Map . BasicSection . Name ) )
{
plugin . Map . BasicSection . Name = Path . GetFileNameWithoutExtension ( saveFilename ) ;
}
2022-10-03 00:03:30 +02:00
// Once saved, leave it to be manually handled on steam publish.
if ( string . IsNullOrEmpty ( plugin . Map . SteamSection . Title ) | | plugin . Map . SteamSection . PublishedFileId = = 0 )
{
plugin . Map . SteamSection . Title = plugin . Map . BasicSection . Name ;
}
ToolType current = ActiveToolType ;
2023-03-10 13:40:04 +01:00
// Different multithreader, so save prompt can start a map load.
saveMultiThreader . ExecuteThreaded (
2023-03-10 21:45:02 +01:00
( ) = > SaveFile ( plugin , saveFilename , fileType , dontResavePreview ) ,
2023-03-10 13:40:04 +01:00
( si ) = > PostSave ( si , afterSaveDone ) , true ,
( bl , str ) = > EnableDisableUi ( bl , str , current , saveMultiThreader ) ,
"Saving map" ) ;
2022-10-03 00:03:30 +02:00
}
2023-07-06 22:48:29 +02:00
private Boolean IdentifyMap ( String loadFilename , out FileType fileType , out GameType gameType , out bool isMegaMap , out string theater )
2022-10-03 00:03:30 +02:00
{
fileType = FileType . None ;
gameType = GameType . None ;
2023-05-31 23:51:28 +02:00
theater = null ;
2023-07-06 22:48:29 +02:00
isMegaMap = false ;
2022-10-03 00:03:30 +02:00
try
{
if ( ! File . Exists ( loadFilename ) )
{
return false ;
}
}
catch
{
return false ;
}
switch ( Path . GetExtension ( loadFilename ) . ToLower ( ) )
{
case ".ini" :
case ".mpr" :
fileType = FileType . INI ;
break ;
case ".bin" :
fileType = FileType . BIN ;
break ;
case ".pgm" :
fileType = FileType . PGM ;
break ;
2023-04-03 21:17:00 +02:00
case ".meg" :
fileType = FileType . MEG ;
break ;
2022-10-03 00:03:30 +02:00
}
INI iniContents = null ;
2022-10-28 15:15:52 +02:00
bool iniWasFetched = false ;
2022-10-03 00:03:30 +02:00
if ( fileType = = FileType . None )
{
long filesize = 0 ;
try
{
filesize = new FileInfo ( loadFilename ) . Length ;
2022-10-28 15:15:52 +02:00
iniWasFetched = true ;
2022-10-03 00:03:30 +02:00
iniContents = GeneralUtils . GetIniContents ( loadFilename , FileType . INI ) ;
2022-10-28 15:15:52 +02:00
if ( iniContents ! = null )
{
fileType = FileType . INI ;
}
2022-10-03 00:03:30 +02:00
}
catch
{
2022-10-28 15:15:52 +02:00
iniContents = null ;
}
if ( iniContents = = null )
{
// Check if it's a classic 64x64 map.
2022-10-03 00:03:30 +02:00
Size tdMax = TiberianDawn . Constants . MaxSize ;
if ( filesize = = tdMax . Width * tdMax . Height * 2 )
{
fileType = FileType . BIN ;
}
else
{
return false ;
}
}
}
string iniFile = fileType ! = FileType . BIN ? loadFilename : Path . ChangeExtension ( loadFilename , ".ini" ) ;
2022-10-28 15:15:52 +02:00
if ( ! iniWasFetched )
2022-10-03 00:03:30 +02:00
{
iniContents = GeneralUtils . GetIniContents ( iniFile , fileType ) ;
}
2022-11-29 14:11:35 +01:00
if ( iniContents = = null | | ! INITools . CheckForIniInfo ( iniContents , "Map" ) | | ! INITools . CheckForIniInfo ( iniContents , "Basic" ) )
2022-10-03 00:03:30 +02:00
{
return false ;
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
theater = ( iniContents [ "Map" ] . TryGetValue ( "Theater" ) ? ? String . Empty ) . ToLower ( ) ;
2022-10-03 00:03:30 +02:00
switch ( fileType )
{
case FileType . INI :
{
2023-02-24 22:08:01 +01:00
gameType = RedAlert . GamePluginRA . CheckForRAMap ( iniContents ) ? GameType . RedAlert : GameType . TiberianDawn ;
2022-10-03 00:03:30 +02:00
break ;
}
case FileType . BIN :
{
gameType = File . Exists ( iniFile ) ? GameType . TiberianDawn : GameType . None ;
break ;
}
case FileType . PGM :
{
try
{
using ( var megafile = new Megafile ( loadFilename ) )
{
if ( megafile . Any ( f = > Path . GetExtension ( f ) . ToLower ( ) = = ".mpr" ) )
{
gameType = GameType . RedAlert ;
}
else
{
gameType = GameType . TiberianDawn ;
}
}
}
catch ( FileNotFoundException )
{
return false ;
}
break ;
}
}
if ( gameType = = GameType . TiberianDawn )
{
2023-07-06 22:48:29 +02:00
isMegaMap = TiberianDawn . GamePluginTD . CheckForMegamap ( iniContents ) ;
2023-02-24 22:08:01 +01:00
if ( SoleSurvivor . GamePluginSS . CheckForSSmap ( iniContents ) )
2022-10-03 00:03:30 +02:00
{
gameType = GameType . SoleSurvivor ;
}
}
2023-07-06 22:48:29 +02:00
else if ( gameType = = GameType . RedAlert )
{
// Not actually used for RA at the moment.
isMegaMap = true ;
}
2022-10-28 15:15:52 +02:00
return gameType ! = GameType . None ;
2022-10-03 00:03:30 +02:00
}
2022-10-02 12:40:55 +02:00
/// <summary>
2022-11-18 15:18:52 +01:00
/// WARNING: this function is meant for map load, meaning it unloads the current plugin in addition to disabling all controls!
2022-10-02 12:40:55 +02:00
/// </summary>
/// <param name="enableUI"></param>
/// <param name="label"></param>
2023-03-10 13:40:04 +01:00
private void LoadUnloadUi ( bool enableUI , string label , SimpleMultiThreading currentMultiThreader )
2022-10-02 12:40:55 +02:00
{
fileNewMenuItem . Enabled = enableUI ;
2023-02-23 00:00:54 +01:00
fileNewFromImageMenuItem . Enabled = enableUI ;
2022-10-02 12:40:55 +02:00
fileOpenMenuItem . Enabled = enableUI ;
fileRecentFilesMenuItem . Enabled = enableUI ;
2023-02-24 22:08:01 +01:00
viewLayersToolStripMenuItem . Enabled = enableUI ;
2022-10-02 12:40:55 +02:00
viewIndicatorsToolStripMenuItem . Enabled = enableUI ;
2022-11-18 15:18:52 +01:00
if ( ! enableUI )
2022-07-12 07:27:13 +02:00
{
2022-10-02 12:40:55 +02:00
Unload ( ) ;
2023-03-10 13:40:04 +01:00
currentMultiThreader . CreateBusyLabel ( this , label ) ;
2022-10-02 12:40:55 +02:00
}
}
/// <summary>
/// The 'lighter' enable/disable UI function, for map saving.
/// </summary>
/// <param name="enableUI"></param>
/// <param name="label"></param>
2023-03-10 13:40:04 +01:00
private void EnableDisableUi ( bool enableUI , string label , ToolType storedToolType , SimpleMultiThreading currentMultiThreader )
2022-10-02 12:40:55 +02:00
{
2022-10-03 00:03:30 +02:00
fileNewMenuItem . Enabled = enableUI ;
2023-02-23 00:00:54 +01:00
fileNewFromImageMenuItem . Enabled = enableUI ;
2022-10-03 00:03:30 +02:00
fileOpenMenuItem . Enabled = enableUI ;
fileRecentFilesMenuItem . Enabled = enableUI ;
2023-02-24 22:08:01 +01:00
viewLayersToolStripMenuItem . Enabled = enableUI ;
2022-10-03 00:03:30 +02:00
viewIndicatorsToolStripMenuItem . Enabled = enableUI ;
2022-10-02 12:40:55 +02:00
EnableDisableMenuItems ( enableUI ) ;
2022-10-03 00:03:30 +02:00
mapPanel . Enabled = enableUI ;
2022-10-02 12:40:55 +02:00
if ( enableUI )
{
2022-10-03 00:03:30 +02:00
RefreshUI ( storedToolType ) ;
2022-07-12 07:27:13 +02:00
}
else
{
2022-10-03 00:03:30 +02:00
ClearActiveTool ( ) ;
foreach ( var toolStripButton in viewToolStripButtons )
{
toolStripButton . Enabled = false ;
}
2023-03-10 13:40:04 +01:00
currentMultiThreader . CreateBusyLabel ( this , label ) ;
2022-10-02 12:40:55 +02:00
}
}
2023-07-06 22:48:29 +02:00
private static IGamePlugin LoadNewPlugin ( GameType gameType , string theater , bool isMegaMap )
2022-10-02 12:40:55 +02:00
{
2023-07-06 22:48:29 +02:00
return LoadNewPlugin ( gameType , theater , isMegaMap , false ) ;
2022-10-02 12:40:55 +02:00
}
2023-07-06 22:48:29 +02:00
private static IGamePlugin LoadNewPlugin ( GameType gameType , string theater , bool isMegaMap , bool noImage )
2022-10-02 12:40:55 +02:00
{
2023-06-09 01:13:25 +02:00
// Get plugin type
IGamePlugin plugin = null ;
2023-06-23 12:24:24 +02:00
RedAlert . GamePluginRA raPlugin = null ;
2023-06-09 01:13:25 +02:00
if ( gameType = = GameType . TiberianDawn )
{
2023-07-06 22:48:29 +02:00
plugin = new TiberianDawn . GamePluginTD ( ! noImage , isMegaMap ) ;
2023-06-09 01:13:25 +02:00
}
else if ( gameType = = GameType . RedAlert )
{
2023-07-06 22:48:29 +02:00
raPlugin = new RedAlert . GamePluginRA ( ! noImage ) ; // isMegaMap);
2023-06-23 12:24:24 +02:00
plugin = raPlugin ;
2023-06-09 01:13:25 +02:00
}
else if ( gameType = = GameType . SoleSurvivor )
{
2023-07-06 22:48:29 +02:00
plugin = new SoleSurvivor . GamePluginSS ( ! noImage , isMegaMap ) ;
2023-06-09 01:13:25 +02:00
}
// Get theater object
TheaterTypeConverter ttc = new TheaterTypeConverter ( ) ;
TheaterType theaterType = ttc . ConvertFrom ( new MapContext ( plugin . Map , false ) , theater ) ;
2023-05-16 21:25:03 +02:00
// Resetting to a specific game type will take care of classic mode.
2023-06-27 11:07:57 +02:00
Globals . TheArchiveManager . Reset ( gameType , theaterType ) ;
2023-05-22 17:33:58 +02:00
Globals . TheGameTextManager . Reset ( gameType ) ;
2023-07-10 17:22:34 +02:00
Globals . TheTilesetManager . Reset ( gameType , theaterType ) ;
2023-06-09 01:13:25 +02:00
Globals . TheTeamColorManager . Reset ( gameType , theaterType ) ;
// Load game-specific data
2022-10-02 12:40:55 +02:00
if ( gameType = = GameType . TiberianDawn )
{
2023-04-25 18:50:13 +02:00
Globals . TheTeamColorManager . Load ( @"DATA\XML\CNCTDTEAMCOLORS.XML" ) ;
2023-06-09 01:13:25 +02:00
AddTeamColorsTD ( Globals . TheTeamColorManager ) ;
2022-10-02 12:40:55 +02:00
}
2023-07-06 22:48:29 +02:00
else if ( gameType = = GameType . RedAlert & & raPlugin ! = null )
2022-10-02 12:40:55 +02:00
{
2023-07-06 22:48:29 +02:00
Byte [ ] rulesFile = Globals . TheArchiveManager . ReadFileClassic ( "rules.ini" ) ;
Byte [ ] rulesUpdFile = Globals . TheArchiveManager . ReadFileClassic ( "aftrmath.ini" ) ;
Byte [ ] rulesMpFile = Globals . TheArchiveManager . ReadFileClassic ( "mplayer.ini" ) ;
// This returns errors in original rules files. Ignore for now.
raPlugin . ReadRules ( rulesFile ) ;
raPlugin . ReadExpandRules ( rulesUpdFile ) ;
raPlugin . ReadMultiRules ( rulesMpFile ) ;
2023-06-23 12:24:24 +02:00
// Only one will be found.
2022-10-02 12:40:55 +02:00
Globals . TheTeamColorManager . Load ( @"DATA\XML\CNCRATEAMCOLORS.XML" ) ;
2023-05-31 23:51:28 +02:00
Globals . TheTeamColorManager . Load ( "palette.cps" ) ;
2023-06-09 01:13:25 +02:00
AddTeamColorsRA ( Globals . TheTeamColorManager ) ;
2022-10-02 12:40:55 +02:00
}
else if ( gameType = = GameType . SoleSurvivor )
{
Globals . TheTeamColorManager . Load ( @"DATA\XML\CNCTDTEAMCOLORS.XML" ) ;
2023-06-09 01:13:25 +02:00
AddTeamColorsTD ( Globals . TheTeamColorManager ) ;
2022-07-12 07:27:13 +02:00
}
2023-07-06 22:48:29 +02:00
// Needs to be done after the whole init, so colors reading is properly initialised.
2023-06-19 02:05:53 +02:00
plugin . Map . FlagColors = plugin . GetFlagColors ( ) ;
2022-10-02 12:40:55 +02:00
return plugin ;
2022-07-12 07:27:13 +02:00
}
2023-06-09 01:13:25 +02:00
private static void AddTeamColorsTD ( ITeamColorManager teamColorManager )
2022-11-18 15:18:52 +01:00
{
2023-06-16 02:03:01 +02:00
// Only applicable for Remastered colors since I can't control those.
2023-04-25 18:50:13 +02:00
if ( teamColorManager is TeamColorManager tcm )
{
2023-06-16 02:03:01 +02:00
// Remaster additions / tweaks
2023-06-09 01:13:25 +02:00
// Neutral
TeamColor teamColorSNeutral = new TeamColor ( tcm ) ;
teamColorSNeutral . Load ( tcm . GetItem ( "GOOD" ) , "NEUTRAL" ) ;
tcm . AddTeamColor ( teamColorSNeutral ) ;
// Special
TeamColor teamColorSpecial = new TeamColor ( tcm ) ;
teamColorSpecial . Load ( tcm . GetItem ( "GOOD" ) , "SPECIAL" ) ;
tcm . AddTeamColor ( teamColorSpecial ) ;
// Black for unowned.
2023-05-22 17:33:58 +02:00
TeamColor teamColorNone = new TeamColor ( tcm ) ;
2023-04-25 18:50:13 +02:00
teamColorNone . Load ( "NONE" , "BASE_TEAM" ,
Color . FromArgb ( 66 , 255 , 0 ) , Color . FromArgb ( 0 , 255 , 56 ) , 0 ,
new Vector3 ( 0.30f , - 1.00f , 0.00f ) , new Vector3 ( 0f , 1f , 1f ) , new Vector2 ( 0.0f , 0.1f ) ,
new Vector3 ( 0 , 1 , 1 ) , new Vector2 ( 0 , 1 ) , Color . FromArgb ( 61 , 61 , 59 ) ) ;
tcm . AddTeamColor ( teamColorNone ) ;
2023-06-16 02:03:01 +02:00
// Extra color for flag 7: metallic blue.
2023-05-31 23:51:28 +02:00
TeamColor teamColorSeven = new TeamColor ( tcm ) ;
teamColorSeven . Load ( tcm . GetItem ( "BAD_UNIT" ) , "MULTI7" ) ;
tcm . AddTeamColor ( teamColorSeven ) ;
2023-06-16 02:03:01 +02:00
// Extra color for flag 8: copy of RA's purple.
2023-05-31 23:51:28 +02:00
TeamColor teamColorEight = new TeamColor ( tcm ) ;
teamColorEight . Load ( "MULTI8" , "BASE_TEAM" ,
2023-04-25 18:50:13 +02:00
Color . FromArgb ( 66 , 255 , 0 ) , Color . FromArgb ( 0 , 255 , 56 ) , 0 ,
new Vector3 ( 0.410f , 0.300f , 0.000f ) , new Vector3 ( 0f , 1f , 1f ) , new Vector2 ( 0.0f , 1.0f ) ,
new Vector3 ( 0 , 1 , 1 ) , new Vector2 ( 0 , 1 ) , Color . FromArgb ( 77 , 13 , 255 ) ) ;
2023-05-31 23:51:28 +02:00
tcm . AddTeamColor ( teamColorEight ) ;
2023-04-25 18:50:13 +02:00
}
}
2023-05-31 23:51:28 +02:00
2023-06-09 01:13:25 +02:00
private static void AddTeamColorsRA ( ITeamColorManager teamColorManager )
{
if ( teamColorManager is TeamColorManager tcm )
{
2023-06-16 02:03:01 +02:00
// Remaster additions / tweaks
2023-06-18 16:56:33 +02:00
// "Neutral" in RA colors seems broken; makes stuff black, so remove it.
tcm . RemoveTeamColor ( "NEUTRAL" ) ;
2023-06-16 02:03:01 +02:00
// Special. Technically color "JP" exists for this, but it's wrong.
2023-06-09 01:13:25 +02:00
TeamColor teamColorSpecial = new TeamColor ( tcm ) ;
teamColorSpecial . Load ( tcm . GetItem ( "SPAIN" ) , "SPECIAL" ) ;
tcm . AddTeamColor ( teamColorSpecial ) ;
}
}
2022-10-02 12:40:55 +02:00
/// <summary>
/// The separate-threaded part for making a new map.
/// </summary>
2023-07-27 16:38:56 +02:00
/// <param name="gameType">Game type</param>
/// <param name="imagePath">Image path, indicating the map is being created from image</param>
/// <param name="theater">Theater of the new map</param>
/// <param name="isTdMegaMap">Is megamap</param>
/// <param name="isSinglePlay">Is singleplayer scenario</param>
/// <param name="showTarget">The form to use as target for showing messages / dialogs on.</param>
2022-10-02 12:40:55 +02:00
/// <returns></returns>
2023-07-27 16:38:56 +02:00
private static MapLoadInfo NewFile ( GameType gameType , String imagePath , string theater , bool isTdMegaMap , bool isSinglePlay , MainForm showTarget )
2020-09-11 23:46:04 +03:00
{
2023-01-10 19:58:20 +01:00
int imageWidth = 0 ;
int imageHeight = 0 ;
2023-02-23 00:00:54 +01:00
Byte [ ] imageData = null ;
2023-01-10 19:58:20 +01:00
if ( imagePath ! = null )
{
try
{
using ( Bitmap bm = new Bitmap ( imagePath ) )
{
2023-04-07 11:11:28 +02:00
bm . SetResolution ( 96 , 96 ) ;
2023-01-10 19:58:20 +01:00
imageWidth = bm . Width ;
imageHeight = bm . Height ;
2023-02-23 00:00:54 +01:00
imageData = ImageUtils . GetImageData ( bm , PixelFormat . Format32bppArgb ) ;
2023-01-10 19:58:20 +01:00
}
}
catch ( Exception ex )
{
List < string > errorMessage = new List < string > ( ) ;
errorMessage . Add ( "Error loading image: " + ex . Message ) ;
#if DEBUG
errorMessage . Add ( ex . StackTrace ) ;
#endif
return new MapLoadInfo ( null , FileType . None , null , errorMessage . ToArray ( ) ) ;
}
}
2023-06-18 16:56:33 +02:00
IGamePlugin plugin = null ;
bool mapLoaded = false ;
2022-10-02 12:40:55 +02:00
try
{
2023-06-27 11:07:57 +02:00
plugin = LoadNewPlugin ( gameType , theater , isTdMegaMap ) ;
2023-01-10 19:58:20 +01:00
// This initialises the theater
2022-10-02 12:40:55 +02:00
plugin . New ( theater ) ;
2023-06-18 16:56:33 +02:00
mapLoaded = true ;
2023-07-27 16:38:56 +02:00
plugin . Map . BasicSection . SoloMission = isSinglePlay ;
2022-10-02 12:40:55 +02:00
if ( SteamworksUGC . IsInit )
{
2023-07-20 01:07:00 +02:00
try
{
plugin . Map . BasicSection . Author = SteamFriends . GetPersonaName ( ) ;
}
catch { /* ignore */ }
2022-10-02 12:40:55 +02:00
}
2023-01-10 19:58:20 +01:00
if ( imageData ! = null )
{
2023-02-23 00:00:54 +01:00
Dictionary < int , string > types = ( Dictionary < int , string > ) showTarget
. Invoke ( ( FunctionInvoker ) ( ( ) = > ShowNewFromImageDialog ( plugin , imageWidth , imageHeight , imageData , showTarget ) ) ) ;
if ( types = = null )
{
return null ;
}
plugin . Map . SetMapTemplatesRaw ( imageData , imageWidth , imageHeight , types , null ) ;
2023-01-10 19:58:20 +01:00
}
2023-06-18 16:56:33 +02:00
return new MapLoadInfo ( null , FileType . None , plugin , null , true ) ;
2022-10-02 12:40:55 +02:00
}
catch ( Exception ex )
{
List < string > errorMessage = new List < string > ( ) ;
2023-01-10 19:58:20 +01:00
if ( ex is ArgumentException argex )
{
errorMessage . Add ( GeneralUtils . RecoverArgExceptionMessage ( argex , false ) ) ;
}
else
{
errorMessage . Add ( ex . Message ) ;
}
2022-10-02 12:40:55 +02:00
#if DEBUG
errorMessage . Add ( ex . StackTrace ) ;
#endif
2023-06-18 16:56:33 +02:00
return new MapLoadInfo ( null , FileType . None , plugin , errorMessage . ToArray ( ) , mapLoaded ) ;
2022-10-02 12:40:55 +02:00
}
}
2023-02-23 00:00:54 +01:00
private static Dictionary < int , string > ShowNewFromImageDialog ( IGamePlugin plugin , int imageWidth , int imageHeight , byte [ ] imageData , MainForm showTarget )
{
Color [ ] mostCommon = ImageUtils . FindMostCommonColors ( 2 , imageData , imageWidth , imageHeight , imageWidth * 4 ) ;
Dictionary < int , string > mappings = new Dictionary < int , string > ( ) ;
2023-02-24 22:08:01 +01:00
// This is ignored in the mappings, but eh. Everything unmapped defaults to clear since that's what the map is initialised with.
2023-02-23 00:00:54 +01:00
if ( mostCommon . Length > 0 )
mappings . Add ( mostCommon [ 0 ] . ToArgb ( ) , "CLEAR1" ) ;
if ( mostCommon . Length > 1 )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
ExplorerComparer expl = new ExplorerComparer ( ) ;
TheaterType theater = plugin . Map . Theater ;
TemplateType tt = plugin . Map . TemplateTypes . Where ( t = > t . ExistsInTheater
//&& (!Globals.FilterTheaterObjects || t.Theaters == null || t.Theaters.Length == 0 || t.Theaters.Contains(plugin.Map.Theater.Name))
& & ( t . Flag & TemplateTypeFlag . Clear ) = = TemplateTypeFlag . DefaultFill
& & ( t . Flag & TemplateTypeFlag . IsGrouped ) = = TemplateTypeFlag . None )
. OrderBy ( t = > t . Name , expl ) . FirstOrDefault ( ) ;
if ( tt ! = null )
{
mappings . Add ( mostCommon [ 1 ] . ToArgb ( ) , tt . Name + ":0" ) ;
}
}
2023-02-23 00:00:54 +01:00
using ( NewFromImageDialog nfi = new NewFromImageDialog ( plugin , imageWidth , imageHeight , imageData , mappings ) )
{
2023-03-12 02:40:33 +01:00
nfi . StartPosition = FormStartPosition . CenterParent ;
2023-02-23 00:00:54 +01:00
if ( nfi . ShowDialog ( showTarget ) = = DialogResult . Cancel )
return null ;
return nfi . Mappings ;
}
}
2022-10-02 12:40:55 +02:00
/// <summary>
/// The separate-threaded part for loading a map.
/// </summary>
2023-07-06 22:48:29 +02:00
/// <param name="loadFilename">File to load.</param>
/// <param name="fileType">Type of the loaded file (detected in advance).</param>
/// <param name="gameType">Game type (detected in advance)</param>
/// <param name="isMegaMap">True if this is a megamap.</param>
2022-10-02 12:40:55 +02:00
/// <returns></returns>
2023-07-06 22:48:29 +02:00
private static MapLoadInfo LoadFile ( string loadFilename , FileType fileType , GameType gameType , string theater , bool isMegaMap )
2022-10-02 12:40:55 +02:00
{
2023-06-18 16:56:33 +02:00
IGamePlugin plugin = null ;
bool mapLoaded = false ;
2022-10-02 12:40:55 +02:00
try
{
2023-07-06 22:48:29 +02:00
plugin = LoadNewPlugin ( gameType , theater , isMegaMap ) ;
2022-10-02 12:40:55 +02:00
string [ ] errors = plugin . Load ( loadFilename , fileType ) . ToArray ( ) ;
2023-06-18 16:56:33 +02:00
mapLoaded = true ;
return new MapLoadInfo ( loadFilename , fileType , plugin , errors , true ) ;
2022-10-02 12:40:55 +02:00
}
catch ( Exception ex )
{
List < string > errorMessage = new List < string > ( ) ;
2023-01-10 19:58:20 +01:00
if ( ex is ArgumentException argex )
{
errorMessage . Add ( GeneralUtils . RecoverArgExceptionMessage ( argex , false ) ) ;
}
else
{
errorMessage . Add ( ex . Message ) ;
}
2022-10-02 12:40:55 +02:00
#if DEBUG
errorMessage . Add ( ex . StackTrace ) ;
#endif
2023-06-18 16:56:33 +02:00
return new MapLoadInfo ( loadFilename , fileType , plugin , errorMessage . ToArray ( ) , mapLoaded ) ;
2022-10-02 12:40:55 +02:00
}
}
2023-03-10 21:45:02 +01:00
private static ( string FileName , bool SavedOk , string error ) SaveFile ( IGamePlugin plugin , string saveFilename , FileType fileType , bool dontResavePreview )
2022-10-03 00:03:30 +02:00
{
try
{
2023-03-10 21:45:02 +01:00
plugin . Save ( saveFilename , fileType , null , dontResavePreview ) ;
2022-10-03 00:03:30 +02:00
return ( saveFilename , true , null ) ;
}
catch ( Exception ex )
{
2023-07-12 09:08:04 +02:00
string errorMessage = "Error saving map: " + ex . Message ;
2022-10-03 00:03:30 +02:00
errorMessage + = "\n\n" + ex . StackTrace ;
return ( saveFilename , false , errorMessage ) ;
}
}
2023-01-10 19:58:20 +01:00
private void PostLoad ( MapLoadInfo loadInfo )
2022-10-02 12:40:55 +02:00
{
2023-02-23 00:00:54 +01:00
if ( loadInfo = = null )
{
// Absolute abort
2023-03-10 13:40:04 +01:00
SimpleMultiThreading . RemoveBusyLabel ( this ) ;
2023-02-23 00:00:54 +01:00
return ;
}
2023-08-03 18:56:02 +02:00
IGamePlugin oldPlugin = this . plugin ;
2022-10-02 12:40:55 +02:00
string [ ] errors = loadInfo . Errors ? ? new string [ 0 ] ;
2022-11-27 20:48:33 +01:00
// Plugin set to null indicates a fatal processing error where no map was loaded at all.
2023-06-18 16:56:33 +02:00
if ( loadInfo . Plugin = = null | | ( loadInfo . Plugin ! = null & & ! loadInfo . MapLoaded ) )
2022-10-02 12:40:55 +02:00
{
2023-06-18 16:56:33 +02:00
// Attempted to load file, loading went OK, but map was not loaded.
if ( loadInfo . FileName ! = null & & loadInfo . Plugin ! = null & & ! loadInfo . MapLoaded )
2022-10-02 12:40:55 +02:00
{
var fileInfo = new FileInfo ( loadInfo . FileName ) ;
mru . Remove ( fileInfo ) ;
}
2022-11-27 20:48:33 +01:00
// In case of actual error, remove label.
2023-03-10 13:40:04 +01:00
SimpleMultiThreading . RemoveBusyLabel ( this ) ;
2022-10-02 12:40:55 +02:00
MessageBox . Show ( string . Format ( "Error loading {0}: {1}" , loadInfo . FileName ? ? "new map" , String . Join ( "\n" , errors ) ) , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
}
else
{
this . plugin = loadInfo . Plugin ;
2022-10-10 12:56:43 +02:00
plugin . FeedBackHandler = this ;
2022-10-02 12:40:55 +02:00
LoadIcons ( plugin ) ;
if ( errors . Length > 0 )
{
using ( ErrorMessageBox emb = new ErrorMessageBox ( ) )
{
2022-11-14 23:13:32 +01:00
emb . Title = "Error Report - " + Path . GetFileName ( loadInfo . FileName ) ;
2022-10-02 12:40:55 +02:00
emb . Errors = errors ;
emb . StartPosition = FormStartPosition . CenterParent ;
emb . ShowDialog ( this ) ;
}
}
2023-04-03 21:17:00 +02:00
#if ! DEVELOPER
// Don't allow re-save as PGM; act as if this is a new map.
if ( loadInfo . FileType = = FileType . PGM | | loadInfo . FileType = = FileType . MEG )
{
bool isRA = loadInfo . Plugin . GameType = = GameType . RedAlert ;
loadInfo . FileType = FileType . INI ;
loadInfo . FileName = null ;
}
#endif
2022-10-02 12:40:55 +02:00
mapPanel . MapImage = plugin . MapImage ;
filename = loadInfo . FileName ;
2023-04-03 21:17:00 +02:00
loadedFileType = loadInfo . FileType ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
if ( Globals . ZoomToBoundsOnLoad )
{
lock ( jumpToBounds_lock )
{
this . jumpToBounds = true ;
}
}
else
2023-03-26 18:40:14 +02:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
ZoomReset ( ) ;
2023-03-26 18:40:14 +02:00
}
2022-10-02 12:40:55 +02:00
url . Clear ( ) ;
2023-08-03 18:56:02 +02:00
CleanupTools ( oldPlugin ? . GameType ? ? GameType . None ) ;
RefreshUI ( oldSelectedTool ) ;
oldSelectedTool = ToolType . None ;
2022-10-02 13:35:27 +02:00
//RefreshActiveTool(); // done by UI refresh
2022-10-02 12:40:55 +02:00
SetTitle ( ) ;
if ( loadInfo . FileName ! = null )
{
var fileInfo = new FileInfo ( loadInfo . FileName ) ;
mru . Add ( fileInfo ) ;
}
}
}
2023-03-10 13:40:04 +01:00
private void PostSave ( ( string FileName , bool SavedOk , string Error ) saveInfo , Action afterSaveDone )
2022-10-02 12:40:55 +02:00
{
2022-10-03 00:03:30 +02:00
var fileInfo = new FileInfo ( saveInfo . FileName ) ;
if ( saveInfo . SavedOk )
2022-08-10 23:59:53 +02:00
{
2022-10-03 00:03:30 +02:00
if ( fileInfo . Exists & & fileInfo . Length > Globals . MaxMapSize )
2022-08-10 23:59:53 +02:00
{
2022-10-03 00:03:30 +02:00
MessageBox . Show ( string . Format ( "Map file exceeds the maximum size of {0} bytes." , Globals . MaxMapSize ) , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
2022-09-20 18:40:27 +02:00
}
2022-10-03 00:03:30 +02:00
plugin . Dirty = false ;
filename = saveInfo . FileName ;
SetTitle ( ) ;
mru . Add ( fileInfo ) ;
2023-03-10 13:40:04 +01:00
afterSaveDone ? . Invoke ( ) ;
2022-09-20 18:40:27 +02:00
}
2022-10-03 00:03:30 +02:00
else
2020-09-11 23:46:04 +03:00
{
2022-10-03 00:03:30 +02:00
MessageBox . Show ( string . Format ( "Error saving {0}: {1}" , saveInfo . FileName , saveInfo . Error , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ) ;
mru . Remove ( fileInfo ) ;
2020-09-11 23:46:04 +03:00
}
}
2022-09-01 13:05:49 +02:00
/// <summary>
/// This clears the UI and plugin in a safe way, ending up with a blank slate.
/// </summary>
private void Unload ( )
2022-08-18 00:41:47 +02:00
{
try
{
url . Clear ( ) ;
// Disable all tools
2023-08-03 18:56:02 +02:00
if ( ActiveToolType ! = ToolType . None )
{
oldSelectedTool = ActiveToolType ;
}
2022-10-02 12:40:55 +02:00
ActiveToolType = ToolType . None ; // Always re-defaults to map anyway, so nicer if nothing is selected during load.
2022-08-18 00:41:47 +02:00
this . ActiveControl = null ;
2023-08-03 18:56:02 +02:00
CleanupTools ( plugin ? . GameType ? ? GameType . None ) ;
2022-08-18 00:41:47 +02:00
// Unlink plugin
IGamePlugin pl = plugin ;
plugin = null ;
2023-05-20 18:40:07 +02:00
// Clean up UI caching
this . lastInfoPoint = new Point ( - 1 , - 1 ) ;
this . lastInfoSubPixelPoint = new Point ( - 1 , - 1 ) ;
this . lastDescription = null ;
2022-10-02 12:40:55 +02:00
// Refresh UI to plugin-less state
RefreshUI ( ) ;
// Reset map panel. Looks odd if the zoom/position is preserved, so zoom out first.
mapPanel . Zoom = 1.0 ;
2022-08-18 00:41:47 +02:00
mapPanel . MapImage = null ;
mapPanel . Invalidate ( ) ;
// Dispose plugin
if ( pl ! = null )
{
pl . Dispose ( ) ;
}
// Unload graphics
2023-07-10 17:22:34 +02:00
Globals . TheTilesetManager . Reset ( GameType . None , null ) ;
2022-08-18 00:41:47 +02:00
// Clean up loaded file status
filename = null ;
loadedFileType = FileType . None ;
SetTitle ( ) ;
}
catch
{
// Ignore.
}
}
2022-10-03 00:03:30 +02:00
private void RefreshUI ( )
2020-09-11 23:46:04 +03:00
{
2022-10-03 00:03:30 +02:00
RefreshUI ( this . activeToolType ) ;
2020-09-11 23:46:04 +03:00
}
2022-10-03 00:03:30 +02:00
private void RefreshUI ( ToolType activeToolType )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
// Menu items
2022-10-02 12:40:55 +02:00
EnableDisableMenuItems ( true ) ;
2022-09-01 13:05:49 +02:00
// Tools
2020-09-11 23:46:04 +03:00
availableToolTypes = ToolType . None ;
if ( plugin ! = null )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
string th = plugin . Map . Theater . Name ;
availableToolTypes | = ToolType . Map ; // Should always show clear terrain, no matter what.
availableToolTypes | = plugin . Map . SmudgeTypes . Any ( t = > ! Globals . FilterTheaterObjects | | t . ExistsInTheater ) ? ToolType . Smudge : ToolType . None ;
availableToolTypes | = plugin . Map . OverlayTypes . Any ( t = > t . IsOverlay & & ( ! Globals . FilterTheaterObjects | | t . ExistsInTheater ) ) ? ToolType . Overlay : ToolType . None ;
2023-03-02 14:56:30 +01:00
availableToolTypes | = plugin . Map . TerrainTypes . Any ( t = > ! Globals . FilterTheaterObjects | | t . Theaters = = null | | t . Theaters . Contains ( th ) ) ? ToolType . Terrain : ToolType . None ;
availableToolTypes | = plugin . Map . InfantryTypes . Any ( ) ? ToolType . Infantry : ToolType . None ;
availableToolTypes | = plugin . Map . UnitTypes . Any ( ) ? ToolType . Unit : ToolType . None ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
availableToolTypes | = plugin . Map . BuildingTypes . Any ( t = > ! Globals . FilterTheaterObjects | | ! t . IsTheaterDependent | | t . ExistsInTheater ) ? ToolType . Building : ToolType . None ;
availableToolTypes | = plugin . Map . OverlayTypes . Any ( t = > t . IsResource & & ( ! Globals . FilterTheaterObjects | | t . ExistsInTheater ) ) ? ToolType . Resources : ToolType . None ;
availableToolTypes | = plugin . Map . OverlayTypes . Any ( t = > t . IsWall & & ( ! Globals . FilterTheaterObjects | | t . ExistsInTheater ) ) ? ToolType . Wall : ToolType . None ;
2023-07-15 13:54:07 +02:00
// Waypoints are always available.
availableToolTypes | = ToolType . Waypoint ;
// Always allow celltrigger tool, even if triggers list is empty; it contains a tooltip saying which trigger types are eligible.
2022-08-14 19:15:17 +02:00
availableToolTypes | = ToolType . CellTrigger ;
2023-03-02 14:56:30 +01:00
// TODO - "Select" tool will always be enabled
//availableToolTypes |= ToolType.Select;
2020-09-11 23:46:04 +03:00
}
2020-09-14 18:13:57 +03:00
foreach ( var toolStripButton in viewToolStripButtons )
{
toolStripButton . Enabled = ( availableToolTypes & toolStripButton . ToolType ) ! = ToolType . None ;
}
2020-09-11 23:46:04 +03:00
ActiveToolType = activeToolType ;
}
2022-10-02 12:40:55 +02:00
private void EnableDisableMenuItems ( bool enable )
2020-09-14 13:40:09 +03:00
{
2022-10-02 12:40:55 +02:00
bool hasPlugin = plugin ! = null ;
fileSaveMenuItem . Enabled = enable & & hasPlugin ;
fileSaveAsMenuItem . Enabled = enable & & hasPlugin ;
filePublishMenuItem . Enabled = enable & & hasPlugin ;
2022-12-28 12:35:15 +01:00
#if DEVELOPER
2022-10-02 12:40:55 +02:00
fileExportMenuItem . Enabled = enable & & hasPlugin ;
2022-12-28 12:35:15 +01:00
#endif
2022-10-02 12:40:55 +02:00
editUndoMenuItem . Enabled = enable & & hasPlugin & & url . CanUndo ;
editRedoMenuItem . Enabled = enable & & hasPlugin & & url . CanRedo ;
editClearUndoRedoMenuItem . Enabled = enable & & hasPlugin & & url . CanUndo | | url . CanRedo ;
settingsMapSettingsMenuItem . Enabled = enable & & hasPlugin ;
settingsTeamTypesMenuItem . Enabled = enable & & hasPlugin ;
settingsTriggersMenuItem . Enabled = enable & & hasPlugin ;
2022-10-13 18:45:23 +02:00
toolsStatsGameObjectsMenuItem . Enabled = enable & & hasPlugin ;
toolsStatsPowerMenuItem . Enabled = enable & & hasPlugin ;
toolsStatsStorageMenuItem . Enabled = enable & & hasPlugin ;
2022-10-02 12:40:55 +02:00
toolsRandomizeTilesMenuItem . Enabled = enable & & hasPlugin ;
toolsExportImageMenuItem . Enabled = enable & & hasPlugin ;
2022-12-28 12:35:15 +01:00
#if DEVELOPER
2022-10-02 12:40:55 +02:00
developerGoToINIMenuItem . Enabled = enable & & hasPlugin ;
developerDebugToolStripMenuItem . Enabled = enable & & hasPlugin ;
developerGenerateMapPreviewDirectoryMenuItem . Enabled = enable & & hasPlugin ;
2022-12-28 12:35:15 +01:00
#endif
2023-02-24 22:08:01 +01:00
viewLayersToolStripMenuItem . Enabled = enable ;
2022-10-02 12:40:55 +02:00
viewIndicatorsToolStripMenuItem . Enabled = enable ;
2023-03-18 01:42:43 +01:00
// Special rules per game. These should be kept identical to those in ImageExportDialog.SetLayers
2022-10-02 12:40:55 +02:00
viewIndicatorsBuildingFakeLabelsMenuItem . Visible = ! hasPlugin | | plugin . GameType = = GameType . RedAlert ;
2023-03-16 19:03:14 +01:00
viewExtraIndicatorsEffectAreaRadiusMenuItem . Visible = ! hasPlugin | | plugin . GameType = = GameType . RedAlert ;
2023-03-18 01:42:43 +01:00
viewLayersBuildingsMenuItem . Visible = ! hasPlugin | | plugin . GameType ! = GameType . SoleSurvivor | | ! Globals . NoOwnedObjectsInSole ;
viewLayersUnitsMenuItem . Visible = ! hasPlugin | | plugin . GameType ! = GameType . SoleSurvivor | | ! Globals . NoOwnedObjectsInSole ;
viewLayersInfantryMenuItem . Visible = ! hasPlugin | | plugin . GameType ! = GameType . SoleSurvivor | | ! Globals . NoOwnedObjectsInSole ;
2022-10-02 12:40:55 +02:00
viewIndicatorsBuildingRebuildLabelsMenuItem . Visible = ! hasPlugin | | plugin . GameType ! = GameType . SoleSurvivor ;
viewIndicatorsFootballAreaMenuItem . Visible = ! hasPlugin | | plugin . GameType = = GameType . SoleSurvivor ;
2023-06-21 12:18:49 +02:00
viewIndicatorsOutlinesMenuItem . Visible = ! hasPlugin | | plugin . GameType ! = GameType . SoleSurvivor ;
2022-10-02 12:40:55 +02:00
}
2022-09-25 12:11:59 +02:00
2023-08-03 18:56:02 +02:00
private void CleanupTools ( GameType gameType )
2022-10-02 12:40:55 +02:00
{
2022-09-01 13:05:49 +02:00
// Tools
2020-09-14 13:40:09 +03:00
ClearActiveTool ( ) ;
2023-08-03 18:56:02 +02:00
if ( oldMockObjects ! = null & & gameType ! = GameType . None & & toolForms . Count > 0 )
{
oldMockGame = gameType ;
}
2020-09-14 13:40:09 +03:00
foreach ( var kvp in toolForms )
{
2023-08-03 18:56:02 +02:00
ITool tool ;
Object obj ;
if ( oldMockObjects ! = null & & gameType ! = GameType . None & & kvp . Value ! = null & & ( tool = kvp . Value . GetTool ( ) ) ! = null & & ( obj = tool . CurrentObject ) ! = null )
{
oldMockObjects . Add ( ( kvp . Key ) , obj ) ;
}
2020-09-14 13:40:09 +03:00
kvp . Value . Dispose ( ) ;
}
toolForms . Clear ( ) ;
}
2020-09-11 23:46:04 +03:00
private void ClearActiveTool ( )
{
2023-07-15 13:54:07 +02:00
if ( activeTool ! = null )
{
activeTool . RequestMouseInfoRefresh - = ViewTool_RequestMouseInfoRefresh ;
activeTool . Deactivate ( ) ;
}
2020-09-11 23:46:04 +03:00
activeTool = null ;
if ( activeToolForm ! = null )
{
activeToolForm . ResizeEnd - = ActiveToolForm_ResizeEnd ;
2020-09-14 13:40:09 +03:00
activeToolForm . Hide ( ) ;
2020-09-11 23:46:04 +03:00
activeToolForm = null ;
}
toolStatusLabel . Text = string . Empty ;
}
private void RefreshActiveTool ( )
{
if ( plugin = = null )
{
return ;
}
if ( activeTool = = null )
{
activeLayers = MapLayerFlag . None ;
}
ClearActiveTool ( ) ;
2023-08-03 18:56:02 +02:00
ToolType curType = ActiveToolType ;
bool found = toolForms . TryGetValue ( curType , out IToolDialog toolDialog ) ;
2022-09-10 02:47:12 +02:00
if ( ! found | | ( toolDialog is Form toolFrm & & toolFrm . IsDisposed ) )
2020-09-14 13:40:09 +03:00
{
2023-08-03 18:56:02 +02:00
switch ( curType )
2020-09-14 13:40:09 +03:00
{
case ToolType . Map :
2020-09-11 23:46:04 +03:00
{
2022-07-12 07:27:13 +02:00
toolDialog = new TemplateToolDialog ( this ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Smudge :
2020-09-11 23:46:04 +03:00
{
2022-08-12 12:12:06 +02:00
toolDialog = new SmudgeToolDialog ( this , plugin ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Overlay :
2020-09-11 23:46:04 +03:00
{
2022-07-12 07:27:13 +02:00
toolDialog = new OverlayToolDialog ( this ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Resources :
2020-09-11 23:46:04 +03:00
{
2022-07-12 07:27:13 +02:00
toolDialog = new ResourcesToolDialog ( this ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Terrain :
2020-09-11 23:46:04 +03:00
{
2022-07-12 07:27:13 +02:00
toolDialog = new TerrainToolDialog ( this , plugin ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Infantry :
2020-09-11 23:46:04 +03:00
{
2022-07-12 07:27:13 +02:00
toolDialog = new InfantryToolDialog ( this , plugin ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Unit :
{
2022-07-12 07:27:13 +02:00
toolDialog = new UnitToolDialog ( this , plugin ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Building :
{
2022-07-12 07:27:13 +02:00
toolDialog = new BuildingToolDialog ( this , plugin ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Wall :
{
2022-07-12 07:27:13 +02:00
toolDialog = new WallsToolDialog ( this ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . Waypoint :
{
2022-07-12 07:27:13 +02:00
toolDialog = new WaypointsToolDialog ( this ) ;
2020-09-14 13:40:09 +03:00
}
break ;
case ToolType . CellTrigger :
{
2022-07-12 07:27:13 +02:00
toolDialog = new CellTriggersToolDialog ( this ) ;
2020-09-14 13:40:09 +03:00
}
break ;
2022-09-19 12:23:44 +02:00
case ToolType . Select :
{
2022-10-02 12:40:55 +02:00
// TODO: select/copy/paste function
2022-09-19 12:23:44 +02:00
toolDialog = null ; // new SelectToolDialog(this);
}
break ;
2020-09-14 13:40:09 +03:00
}
if ( toolDialog ! = null )
{
2023-08-03 18:56:02 +02:00
toolForms [ curType ] = toolDialog ;
2020-09-14 13:40:09 +03:00
}
2020-09-11 23:46:04 +03:00
}
2022-09-01 13:05:49 +02:00
MapLayerFlag active = ActiveLayers ;
// Save some processing by just always removing this one.
2022-09-22 01:26:46 +02:00
if ( plugin . GameType = = GameType . TiberianDawn | | plugin . GameType = = GameType . SoleSurvivor )
2022-09-01 13:05:49 +02:00
{
active & = ~ MapLayerFlag . BuildingFakes ;
}
2020-09-14 13:40:09 +03:00
if ( toolDialog ! = null )
2020-09-11 23:46:04 +03:00
{
2020-09-14 13:40:09 +03:00
activeToolForm = ( Form ) toolDialog ;
2023-07-09 03:35:22 +02:00
ITool oldTool = toolDialog . GetTool ( ) ;
Object mockObject = null ;
2023-08-03 18:56:02 +02:00
bool fromBackup = false ;
if ( oldMockGame ! = this . plugin . GameType & & oldMockObjects ! = null & & oldMockObjects . Count ( ) > 0 )
{
oldMockObjects . Clear ( ) ;
oldMockGame = GameType . None ;
}
2023-07-09 03:35:22 +02:00
if ( oldTool ! = null & & oldTool . Plugin = = plugin )
{
// Same map edit session; restore old data
mockObject = oldTool . CurrentObject ;
}
2023-08-03 18:56:02 +02:00
else if ( oldMockGame = = this . plugin . GameType & & oldMockObjects ! = null & & oldMockObjects . TryGetValue ( curType , out object mock ) )
{
mockObject = mock ;
// Retrieve once and remove.
oldMockObjects . Remove ( curType ) ;
fromBackup = true ;
}
2022-09-19 12:23:44 +02:00
// Creates the actual Tool class
2022-09-01 13:05:49 +02:00
toolDialog . Initialize ( mapPanel , active , toolStatusLabel , mouseToolTip , plugin , url ) ;
2020-09-14 13:40:09 +03:00
activeTool = toolDialog . GetTool ( ) ;
2023-07-09 16:54:11 +02:00
// If an active House is set, and the current tool has a techno type, copy it out so its house can be adjusted.
if ( plugin . ActiveHouse ! = null & & mockObject = = null & & activeTool . CurrentObject is ITechno )
{
mockObject = activeTool . CurrentObject ;
}
// If an active House is set, and the mock object is an ITechno, adjust its house (regardless of source)
if ( plugin . ActiveHouse ! = null & & mockObject is ITechno techno )
{
techno . House = plugin . ActiveHouse ;
}
2023-08-03 18:56:02 +02:00
if ( fromBackup & & mockObject is ITechno trtechno )
{
// Do not inherit trigger names from a different session.
trtechno . Trigger = Trigger . None ;
}
2023-07-09 16:54:11 +02:00
// Sets backed up / adjusted object in current tool.
if ( mockObject ! = null )
{
activeTool . CurrentObject = mockObject ;
}
2023-07-15 13:54:07 +02:00
// Allow the tool to refresh the cell info under the mouse cursor.
activeTool . RequestMouseInfoRefresh + = ViewTool_RequestMouseInfoRefresh ;
2022-07-17 00:24:28 +02:00
activeToolForm . ResizeEnd - = ActiveToolForm_ResizeEnd ;
activeToolForm . Shown - = this . ActiveToolForm_Shown ;
activeToolForm . Shown + = this . ActiveToolForm_Shown ;
2020-09-14 13:40:09 +03:00
activeToolForm . Show ( this ) ;
activeTool . Activate ( ) ;
2020-09-11 23:46:04 +03:00
activeToolForm . ResizeEnd + = ActiveToolForm_ResizeEnd ;
}
2022-09-22 01:26:46 +02:00
if ( plugin . IsMegaMap )
2020-09-11 23:46:04 +03:00
{
2022-09-22 01:26:46 +02:00
mapPanel . MaxZoom = 16 ;
mapPanel . ZoomStep = 0.2 ;
}
else
{
mapPanel . MaxZoom = 8 ;
mapPanel . ZoomStep = 0.15 ;
2020-09-11 23:46:04 +03:00
}
2020-09-14 18:13:57 +03:00
// Refresh toolstrip button checked states
foreach ( var toolStripButton in viewToolStripButtons )
{
2023-08-03 18:56:02 +02:00
toolStripButton . Checked = curType = = toolStripButton . ToolType ;
2020-09-14 18:13:57 +03:00
}
2022-10-02 13:35:27 +02:00
// this somehow fixes the fact that the keyUp and keyDown events of the navigation widget don't come through.
mainToolStrip . Focus ( ) ;
mapPanel . Focus ( ) ;
// refresh for tool
2020-09-11 23:46:04 +03:00
UpdateVisibleLayers ( ) ;
2022-10-02 13:35:27 +02:00
// refresh to paint the actual tool's post-render layers
2020-09-11 23:46:04 +03:00
mapPanel . Invalidate ( ) ;
}
2022-07-17 00:24:28 +02:00
private void ClampActiveToolForm ( )
2020-09-11 23:46:04 +03:00
{
2022-07-17 00:24:28 +02:00
ClampForm ( activeToolForm ) ;
}
public static void ClampForm ( Form toolform )
{
if ( toolform = = null )
2020-09-11 23:46:04 +03:00
{
return ;
}
2022-10-09 15:30:28 +02:00
Size maxAllowed = Globals . MinimumClampSize ;
Rectangle toolBounds = toolform . DesktopBounds ;
if ( maxAllowed = = Size . Empty )
{
maxAllowed = toolform . Size ;
}
else
{
maxAllowed = new Size ( Math . Min ( maxAllowed . Width , toolBounds . Width ) , Math . Min ( maxAllowed . Height , toolBounds . Height ) ) ;
}
2022-07-17 00:24:28 +02:00
Rectangle workingArea = Screen . FromControl ( toolform ) . WorkingArea ;
2022-10-09 15:30:28 +02:00
if ( toolBounds . Left + maxAllowed . Width > workingArea . Right )
2020-09-11 23:46:04 +03:00
{
2022-10-09 15:30:28 +02:00
toolBounds . X = workingArea . Right - maxAllowed . Width ;
2020-09-11 23:46:04 +03:00
}
2022-10-09 15:30:28 +02:00
if ( toolBounds . X + toolBounds . Width - maxAllowed . Width < workingArea . Left )
2020-09-11 23:46:04 +03:00
{
2022-10-09 15:30:28 +02:00
toolBounds . X = workingArea . Left - toolBounds . Width + maxAllowed . Width ;
2020-09-11 23:46:04 +03:00
}
2022-10-09 15:30:28 +02:00
if ( toolBounds . Top + maxAllowed . Height > workingArea . Bottom )
2020-09-11 23:46:04 +03:00
{
2022-10-09 15:30:28 +02:00
toolBounds . Y = workingArea . Bottom - maxAllowed . Height ;
2020-09-11 23:46:04 +03:00
}
2022-10-09 15:30:28 +02:00
// Leave this; don't allow it to disappear under the top
if ( toolBounds . Y < workingArea . Top )
2020-09-11 23:46:04 +03:00
{
2022-10-09 15:30:28 +02:00
toolBounds . Y = workingArea . Top ;
2020-09-11 23:46:04 +03:00
}
2022-10-09 15:30:28 +02:00
toolform . DesktopBounds = toolBounds ;
2020-09-11 23:46:04 +03:00
}
private void ActiveToolForm_ResizeEnd ( object sender , EventArgs e )
{
2022-07-17 00:24:28 +02:00
ClampActiveToolForm ( ) ;
}
private void ActiveToolForm_Shown ( object sender , EventArgs e )
{
Form tool = sender as Form ;
if ( tool ! = null )
{
ClampForm ( tool ) ;
}
2020-09-11 23:46:04 +03:00
}
private void UpdateVisibleLayers ( )
{
MapLayerFlag layers = MapLayerFlag . All ;
2023-03-16 23:38:21 +01:00
if ( ! viewIndicatorsMapBoundariesMenuItem . Checked )
2020-09-11 23:46:04 +03:00
{
layers & = ~ MapLayerFlag . Boundaries ;
}
2023-03-16 19:03:14 +01:00
if ( ! viewExtraIndicatorsMapSymmetryMenuItem . Checked )
2022-11-14 23:13:32 +01:00
{
layers & = ~ MapLayerFlag . MapSymmetry ;
}
2023-03-16 19:03:14 +01:00
if ( ! viewExtraIndicatorsMapGridMenuItem . Checked )
2023-02-24 22:08:01 +01:00
{
layers & = ~ MapLayerFlag . MapGrid ;
}
if ( ! viewLayersBuildingsMenuItem . Checked )
2020-09-11 23:46:04 +03:00
{
2022-09-01 13:05:49 +02:00
layers & = ~ MapLayerFlag . Buildings ;
}
2023-02-24 22:08:01 +01:00
if ( ! viewLayersUnitsMenuItem . Checked )
2022-09-01 13:05:49 +02:00
{
layers & = ~ MapLayerFlag . Units ;
}
2023-02-24 22:08:01 +01:00
if ( ! viewLayersInfantryMenuItem . Checked )
2022-09-01 13:05:49 +02:00
{
layers & = ~ MapLayerFlag . Infantry ;
2020-09-11 23:46:04 +03:00
}
2023-02-24 22:08:01 +01:00
if ( ! viewLayersTerrainMenuItem . Checked )
2020-09-11 23:46:04 +03:00
{
layers & = ~ MapLayerFlag . Terrain ;
}
2023-02-24 22:08:01 +01:00
if ( ! viewLayersOverlayMenuItem . Checked )
2022-09-01 13:05:49 +02:00
{
layers & = ~ MapLayerFlag . OverlayAll ;
}
2023-02-24 22:08:01 +01:00
if ( ! viewLayersSmudgeMenuItem . Checked )
2022-09-01 13:05:49 +02:00
{
layers & = ~ MapLayerFlag . Smudge ;
}
2023-02-24 22:08:01 +01:00
if ( ! viewLayersWaypointsMenuItem . Checked )
2020-09-11 23:46:04 +03:00
{
layers & = ~ MapLayerFlag . Waypoints ;
}
2022-09-22 01:26:46 +02:00
if ( ! viewIndicatorsWaypointsMenuItem . Checked )
{
layers & = ~ MapLayerFlag . WaypointsIndic ;
}
2022-09-01 13:05:49 +02:00
if ( ! viewIndicatorsCellTriggersMenuItem . Checked )
2020-09-11 23:46:04 +03:00
{
layers & = ~ MapLayerFlag . CellTriggers ;
}
2022-09-01 13:05:49 +02:00
if ( ! viewIndicatorsObjectTriggersMenuItem . Checked )
2020-09-11 23:46:04 +03:00
{
layers & = ~ MapLayerFlag . TechnoTriggers ;
}
2022-09-01 13:05:49 +02:00
if ( ! viewIndicatorsBuildingFakeLabelsMenuItem . Checked )
{
layers & = ~ MapLayerFlag . BuildingFakes ;
}
if ( ! viewIndicatorsBuildingRebuildLabelsMenuItem . Checked )
{
layers & = ~ MapLayerFlag . BuildingRebuild ;
}
2022-09-25 12:11:59 +02:00
if ( ! viewIndicatorsFootballAreaMenuItem . Checked )
{
layers & = ~ MapLayerFlag . FootballArea ;
}
2023-03-16 19:03:14 +01:00
if ( ! viewExtraIndicatorsWaypointRevealRadiusMenuItem . Checked )
2023-03-12 02:40:33 +01:00
{
layers & = ~ MapLayerFlag . WaypointRadius ;
}
2023-03-16 19:03:14 +01:00
if ( ! viewExtraIndicatorsEffectAreaRadiusMenuItem . Checked )
2023-03-12 02:40:33 +01:00
{
2023-03-16 19:03:14 +01:00
layers & = ~ MapLayerFlag . EffectRadius ;
2023-03-12 02:40:33 +01:00
}
2023-06-22 01:23:52 +02:00
if ( ! viewExtraIndicatorsMapPassabilityMenuItem . Checked )
{
layers & = ~ MapLayerFlag . LandTypes ;
}
2023-06-21 12:18:49 +02:00
if ( ! viewIndicatorsOutlinesMenuItem . Checked )
2023-03-18 01:42:43 +01:00
{
2023-06-21 12:18:49 +02:00
layers & = ~ MapLayerFlag . OverlapOutlines ;
2023-03-18 01:42:43 +01:00
}
2020-09-11 23:46:04 +03:00
ActiveLayers = layers ;
}
2022-12-28 12:35:15 +01:00
#endregion
2022-10-03 00:03:30 +02:00
private void mainToolStripButton_Click ( object sender , EventArgs e )
{
if ( plugin = = null )
{
return ;
}
ActiveToolType = ( ( ViewToolStripButton ) sender ) . ToolType ;
}
private void MapPanel_DragEnter ( object sender , System . Windows . Forms . DragEventArgs e )
{
if ( e . Data . GetDataPresent ( DataFormats . FileDrop ) )
{
String [ ] files = ( String [ ] ) e . Data . GetData ( DataFormats . FileDrop ) ;
if ( files . Length = = 1 )
e . Effect = DragDropEffects . Copy ;
}
}
private void MapPanel_DragDrop ( object sender , System . Windows . Forms . DragEventArgs e )
{
String [ ] files = ( String [ ] ) e . Data . GetData ( DataFormats . FileDrop ) ;
if ( files . Length ! = 1 )
return ;
2023-06-21 12:18:49 +02:00
OpenFileAsk ( files [ 0 ] ) ;
2022-10-03 00:03:30 +02:00
}
2022-09-01 13:05:49 +02:00
private void ViewMenuItem_CheckedChanged ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
UpdateVisibleLayers ( ) ;
}
2023-03-16 19:03:14 +01:00
private void ViewLayersEnableAllMenuItem_Click ( object sender , EventArgs e )
2022-09-01 13:05:49 +02:00
{
2023-03-19 00:06:21 +01:00
EnableDisableLayersCategory ( true , true ) ;
2022-09-01 13:05:49 +02:00
}
2023-03-16 19:03:14 +01:00
private void ViewLayersDisableAllMenuItem_Click ( object sender , EventArgs e )
2022-09-01 13:05:49 +02:00
{
2023-03-19 00:06:21 +01:00
EnableDisableLayersCategory ( true , false ) ;
2022-09-01 13:05:49 +02:00
}
2023-03-19 00:06:21 +01:00
private void ViewIndicatorsEnableAllToolStripMenuItem_Click ( Object sender , EventArgs e )
2023-03-16 19:03:14 +01:00
{
2023-03-19 00:06:21 +01:00
EnableDisableLayersCategory ( false , true ) ;
2023-03-16 19:03:14 +01:00
}
2023-03-19 00:06:21 +01:00
private void ViewIndicatorsDisableAllToolStripMenuItem_Click ( Object sender , EventArgs e )
{
EnableDisableLayersCategory ( false , false ) ;
}
private void EnableDisableLayersCategory ( bool baseLayers , bool enabled )
2022-09-01 13:05:49 +02:00
{
ITool activeTool = this . activeTool ;
try
{
// Suppress updates.
this . activeTool = null ;
2023-03-19 00:06:21 +01:00
SwitchLayers ( baseLayers , enabled ) ;
2022-09-01 13:05:49 +02:00
}
finally
{
// Re-enable tool, force refresh.
MapLayerFlag layerBackup = this . activeLayers ;
// Clear without refresh
this . activeLayers = MapLayerFlag . None ;
// Restore tool
this . activeTool = activeTool ;
// Set with refresh
ActiveLayers = layerBackup ;
}
}
2023-03-19 00:06:21 +01:00
private void SwitchLayers ( bool baseLayers , bool enabled )
2022-09-01 13:05:49 +02:00
{
2023-03-19 00:06:21 +01:00
if ( baseLayers )
2022-09-01 13:05:49 +02:00
{
2023-03-19 00:06:21 +01:00
viewLayersBuildingsMenuItem . Checked = enabled ;
viewLayersInfantryMenuItem . Checked = enabled ;
viewLayersUnitsMenuItem . Checked = enabled ;
viewLayersTerrainMenuItem . Checked = enabled ;
viewLayersOverlayMenuItem . Checked = enabled ;
viewLayersSmudgeMenuItem . Checked = enabled ;
viewLayersWaypointsMenuItem . Checked = enabled ;
2022-09-01 13:05:49 +02:00
}
2023-03-19 00:06:21 +01:00
else
2022-09-01 13:05:49 +02:00
{
2023-03-19 00:06:21 +01:00
viewIndicatorsMapBoundariesMenuItem . Checked = enabled ;
viewIndicatorsWaypointsMenuItem . Checked = enabled ;
viewIndicatorsFootballAreaMenuItem . Checked = enabled ;
viewIndicatorsCellTriggersMenuItem . Checked = enabled ;
viewIndicatorsObjectTriggersMenuItem . Checked = enabled ;
viewIndicatorsBuildingRebuildLabelsMenuItem . Checked = enabled ;
viewIndicatorsBuildingFakeLabelsMenuItem . Checked = enabled ;
2023-06-21 12:18:49 +02:00
viewIndicatorsOutlinesMenuItem . Checked = enabled ;
2022-09-01 13:05:49 +02:00
}
}
2023-01-10 19:58:20 +01:00
private void DeveloperGoToINIMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
#if DEVELOPER
if ( ( plugin = = null ) | | string . IsNullOrEmpty ( filename ) )
{
return ;
}
var path = Path . ChangeExtension ( filename , ".mpr" ) ;
if ( ! File . Exists ( path ) )
{
path = Path . ChangeExtension ( filename , ".ini" ) ;
}
try
{
2022-07-14 22:19:01 +02:00
System . Diagnostics . Process . Start ( path ) ;
2020-09-11 23:46:04 +03:00
}
2022-07-14 22:19:01 +02:00
catch ( System . ComponentModel . Win32Exception )
2020-09-11 23:46:04 +03:00
{
2022-07-14 22:19:01 +02:00
System . Diagnostics . Process . Start ( "notepad.exe" , path ) ;
2020-09-11 23:46:04 +03:00
}
catch ( Exception ) { }
#endif
}
2023-01-10 19:58:20 +01:00
private void DeveloperGenerateMapPreviewDirectoryMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
#if DEVELOPER
FolderBrowserDialog fbd = new FolderBrowserDialog
{
ShowNewFolderButton = false
} ;
2022-10-02 12:40:55 +02:00
if ( fbd . ShowDialog ( ) ! = DialogResult . OK )
2020-09-11 23:46:04 +03:00
{
2022-10-02 12:40:55 +02:00
return ;
}
var extensions = new string [ ] { ".ini" , ".mpr" } ;
foreach ( var file in Directory . EnumerateFiles ( fbd . SelectedPath ) . Where ( file = > extensions . Contains ( Path . GetExtension ( file ) . ToLower ( ) ) ) )
{
bool valid = GetPluginOptions ( file , out FileType fileType , out GameType gameType , out bool isTdMegaMap ) ;
IGamePlugin plugin = LoadNewPlugin ( gameType , isTdMegaMap , null , true ) ;
plugin . Load ( file , fileType ) ;
plugin . Map . GenerateMapPreview ( gameType , true ) . Save ( Path . ChangeExtension ( file , ".tga" ) ) ;
plugin . Dispose ( ) ;
2020-09-11 23:46:04 +03:00
}
#endif
}
2023-01-10 19:58:20 +01:00
private void DeveloperDebugShowOverlapCellsMenuItem_CheckedChanged ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
#if DEVELOPER
Globals . Developer . ShowOverlapCells = developerDebugShowOverlapCellsMenuItem . Checked ;
#endif
}
2023-01-10 19:58:20 +01:00
private void FilePublishMenuItem_Click ( object sender , EventArgs e )
2020-09-11 23:46:04 +03:00
{
if ( plugin = = null )
{
return ;
}
2022-09-25 12:11:59 +02:00
if ( plugin . GameType = = GameType . SoleSurvivor )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
MessageBox . Show ( "Sole Survivor maps cannot be published to the Steam Workshop; they are not usable by the C&C Remastered Collection." , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
2022-09-25 12:11:59 +02:00
return ;
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
if ( plugin . Map . Theater . IsModTheater ) // || plugin.Map.Theater.Tilesets.Count() == 0)
{
if ( ! plugin . Map . BasicSection . SoloMission )
{
MessageBox . Show ( "This map uses a nonstandard theater that is not usable by the C&C Remastered Collection. To avoid issues, these can not be published to the Steam Workshop." , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
return ;
}
// If the mission is already published on Steam, don't bother asking this and just continue.
if ( plugin . Map . SteamSection . PublishedFileId = = 0
& & DialogResult . Yes ! = MessageBox . Show ( "This map uses a nonstandard theater that is not usable by the C&C Remastered Collection without modding!" +
" Are you sure you want to publish a mission that will be incompatible with the standard unmodded game?" , "Warning" ,
MessageBoxButtons . YesNo , MessageBoxIcon . Warning , MessageBoxDefaultButton . Button2 ) )
{
return ;
}
}
2022-09-25 12:11:59 +02:00
if ( plugin . GameType = = GameType . TiberianDawn & & plugin . IsMegaMap )
{
2023-07-24 18:43:50 +02:00
if ( ! plugin . Map . BasicSection . SoloMission )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
MessageBox . Show ( "Tiberian Dawn multiplayer megamaps cannot be published to the Steam Workshop; they are not usable by the C&C Remastered Collection without modding, and may cause issues on the official servers." , "Warning" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
2023-07-24 18:43:50 +02:00
return ;
}
// If the mission is already published on Steam, don't bother asking this and just continue.
if ( plugin . Map . SteamSection . PublishedFileId = = 0
& & DialogResult . Yes ! = MessageBox . Show ( "Megamaps are not supported by Tiberian Dawn Remastered without modding!" +
" Are you sure you want to publish a mission that will be incompatible with the standard unmodded game?" , "Warning" ,
MessageBoxButtons . YesNo , MessageBoxIcon . Warning , MessageBoxDefaultButton . Button2 ) )
{
return ;
}
2022-09-25 12:11:59 +02:00
}
2022-09-29 07:50:41 +02:00
if ( ! SteamworksUGC . IsInit )
{
MessageBox . Show ( "Steam interface is not initialized. To enable Workshop publishing, log into Steam and restart the editor." , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
return ;
}
2023-03-10 13:40:04 +01:00
PromptSaveMap ( ShowPublishDialog , false ) ;
}
private void ShowPublishDialog ( )
{
2020-09-11 23:46:04 +03:00
if ( plugin . Dirty )
{
MessageBox . Show ( "Map must be saved before publishing." , "Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
return ;
}
if ( new FileInfo ( filename ) . Length > Globals . MaxMapSize )
{
return ;
}
2022-10-03 00:03:30 +02:00
// Check if we need to save.
ulong oldId = plugin . Map . SteamSection . PublishedFileId ;
string oldName = plugin . Map . SteamSection . Title ;
string oldDescription = plugin . Map . SteamSection . Description ;
string oldPreview = plugin . Map . SteamSection . PreviewFile ;
2023-06-21 12:18:49 +02:00
string oldVisibility = plugin . Map . SteamSection . Visibility ;
2022-10-03 00:03:30 +02:00
// Open publish dialog
bool wasPublished ;
2020-09-11 23:46:04 +03:00
using ( var sd = new SteamDialog ( plugin ) )
{
sd . ShowDialog ( ) ;
2022-10-03 00:03:30 +02:00
wasPublished = sd . MapWasPublished ;
}
// Only re-save is it was published and something actually changed.
if ( wasPublished & & ( oldId ! = plugin . Map . SteamSection . PublishedFileId
| | oldName ! = plugin . Map . SteamSection . Title
| | oldDescription ! = plugin . Map . SteamSection . Description
| | oldPreview ! = plugin . Map . SteamSection . PreviewFile
2023-06-21 12:18:49 +02:00
| | oldVisibility ! = plugin . Map . SteamSection . Visibility ) )
2022-10-03 00:03:30 +02:00
{
// This takes care of saving the Steam info into the map.
2023-03-10 21:45:02 +01:00
// This specific overload only saves the map, without resaving the preview.
2023-08-03 18:56:02 +02:00
SaveAction ( true , null , false , false ) ;
2020-09-11 23:46:04 +03:00
}
}
2023-10-18 18:05:23 +02:00
private void InfoAboutMenuItem_Click ( Object sender , EventArgs e )
{
StringBuilder editorInfo = new StringBuilder ( ) ;
2023-10-23 19:59:46 +02:00
editorInfo . Append ( Program . ProgramVersionTitle ) . Append ( '\n' ) . Append ( '\n' )
2023-10-18 18:05:23 +02:00
. Append ( Program . ProgramInfo ) . Append ( '\n' )
. Append ( '\n' ) . Append ( '\n' )
. Append ( "For info and updates, go to \"" ) . Append ( infoToolStripMenuItem . Text ) . Append ( "\" → \"" ) . Append ( InfoWebsiteMenuItem . Text ) . Append ( "\"" ) ;
2023-10-23 19:59:46 +02:00
MessageBox . Show ( this , editorInfo . ToString ( ) , Program . ProgramVersionTitle , MessageBoxButtons . OK , MessageBoxIcon . Information ) ;
2023-10-18 18:05:23 +02:00
}
private void InfoWebsiteMenuItem_Click ( Object sender , EventArgs e )
{
Process . Start ( "https://github.com/" + Program . GithubOwner + "/" + Program . GithubProject ) ;
}
private async void InfoCheckForUpdatesMenuItem_Click ( Object sender , EventArgs e )
{
2023-10-23 19:59:46 +02:00
string title = Program . ProgramVersionTitle ;
2023-10-18 18:05:23 +02:00
if ( this . startedUpdate )
{
MessageBox . Show ( this , "Update check already started. Please wait." , title , MessageBoxButtons . OK ) ;
return ;
}
this . startedUpdate = true ;
this . SetTitle ( ) ;
const string checkError = "An error occurred when checking the version:" ;
AssemblyName assn = Assembly . GetExecutingAssembly ( ) . GetName ( ) ;
System . Version curVer = assn . Version ;
Uri downloadUri = new Uri ( "https://api.github.com/repos/" + Program . GithubOwner + "/" + Program . GithubProject + "/releases?per_page=1" ) ;
//Uri downloadUri = new Uri("https://store.steampowered.com/");
byte [ ] content = null ;
String returnMessage = null ;
try
{
try
{
using ( HttpClient client = new HttpClient ( ) )
using ( HttpRequestMessage request = new HttpRequestMessage ( HttpMethod . Get , downloadUri ) )
{
// GitHub API won't accept the request without header.
ProductInfoHeaderValue productValue = new ProductInfoHeaderValue ( "GithubProject" , curVer . ToString ( ) ) ;
ProductInfoHeaderValue commentValue = new ProductInfoHeaderValue ( "(https://github.com/" + Program . GithubOwner + "/" + Program . GithubProject + ")" ) ;
request . Headers . UserAgent . Add ( productValue ) ;
request . Headers . UserAgent . Add ( commentValue ) ;
HttpResponseMessage response = await client . SendAsync ( request ) ;
using ( var bytes = new MemoryStream ( ) )
{
await response . Content . CopyToAsync ( bytes ) ;
content = bytes . ToArray ( ) ;
}
}
}
catch ( Exception ex )
{
string type = ex . GetType ( ) . Name ;
string message = ex . Message ;
if ( ex . InnerException is WebException wex )
{
type = wex . GetType ( ) . Name ;
message = wex . Message ;
}
returnMessage = checkError + "\n\n" + type + ": " + message ;
return ;
}
if ( content = = null | | content . Length = = 0 )
{
returnMessage = checkError + "\n\nThe response from the server contained no data." ;
return ;
}
String text = Encoding . UTF8 . GetString ( content ) ;
// search string (can't be bothered parsing) is
Regex regex = new Regex ( "\"tag_name\":\\s*\"v((\\d+)\\.(\\d+)(\\.(\\d+))?(\\.(\\d+))?)\"" ) ;
Match match = regex . Match ( text ) ;
const string dots = " (...)" ;
const int maxLen = 1500 - 6 ;
if ( ! match . Success )
{
if ( text . Length > maxLen )
{
text = text . Substring ( 0 , maxLen ) + dots ;
}
text = text . Trim ( '\r' , '\n' ) ;
returnMessage = checkError + " could not find version in returned data.\n\nReturned data:\n" + text ;
return ;
}
string versionMajStr = match . Groups [ 2 ] . Value ;
string versionMinStr = match . Groups [ 3 ] . Value ;
string versionBldStr = match . Groups [ 5 ] . Value ;
string versionRevStr = match . Groups [ 7 ] . Value ;
int versionMaj = String . IsNullOrEmpty ( versionMajStr ) ? 0 : Int32 . Parse ( versionMajStr ) ;
int versionMin = String . IsNullOrEmpty ( versionMinStr ) ? 0 : Int32 . Parse ( versionMinStr ) ;
int versionBld = String . IsNullOrEmpty ( versionBldStr ) ? 0 : Int32 . Parse ( versionBldStr ) ;
int versionRev = String . IsNullOrEmpty ( versionRevStr ) ? 0 : Int32 . Parse ( versionRevStr ) ;
System . Version serverVer = new System . Version ( versionMaj , versionMin , versionBld , versionRev ) ;
StringBuilder versionMessage = new StringBuilder ( ) ;
if ( curVer < serverVer )
{
versionMessage . Append ( "A newer version " ) . Append ( serverVer . ToString ( ) ) . Append ( " was released on GitHub.\n\n" )
. Append ( "To get the latest version, go to " )
. Append ( "\"" ) . Append ( infoToolStripMenuItem . Text ) . Append ( "\" → \"" ) . Append ( InfoWebsiteMenuItem . Text ) . Append ( "\"" )
. Append ( " and check the \"Releases\" section." ) ;
}
else
{
versionMessage . Append ( "The latest version on GitHub is " ) . Append ( serverVer . ToString ( ) ) . Append ( ". " ) ;
versionMessage . Append ( curVer = = serverVer ? "You are up to date." : "Looks like you're using a super-exclusive unreleased version!" ) ;
}
returnMessage = versionMessage . ToString ( ) ;
}
finally
{
this . startedUpdate = false ;
this . SetTitle ( ) ;
if ( returnMessage ! = null )
{
MessageBox . Show ( this , returnMessage , title ) ;
}
}
}
2023-01-10 19:58:20 +01:00
private void MainToolStrip_MouseMove ( object sender , MouseEventArgs e )
2020-09-11 23:46:04 +03:00
{
2022-09-19 12:23:44 +02:00
if ( Form . ActiveForm ! = null )
{
mainToolStrip . Focus ( ) ;
}
2020-09-11 23:46:04 +03:00
}
2022-08-14 19:15:17 +02:00
2022-07-12 07:27:13 +02:00
private void MainForm_Shown ( object sender , System . EventArgs e )
{
2023-08-03 18:56:02 +02:00
CleanupTools ( GameType . None ) ;
2022-10-02 12:40:55 +02:00
RefreshUI ( ) ;
2022-09-08 09:55:29 +02:00
UpdateUndoRedo ( ) ;
2022-07-12 07:27:13 +02:00
if ( filename ! = null )
2023-06-21 12:18:49 +02:00
OpenFile ( filename ) ;
2022-07-12 07:27:13 +02:00
}
2020-09-11 23:46:04 +03:00
private void MainForm_FormClosing ( object sender , FormClosingEventArgs e )
{
2023-03-10 13:40:04 +01:00
// Form.Close() after the save will re-trigger this FormClosing event handler, but the
// plugin will not be dirty, so it will just succeed and go on to the CleanupOnClose() call.
// Also note, if the save fails for some reason, Form.Close() is never called.
Boolean abort = ! PromptSaveMap ( this . Close , true ) ;
e . Cancel = abort ;
if ( ! abort )
2022-09-01 13:05:49 +02:00
{
2023-03-10 13:40:04 +01:00
CleanupOnClose ( ) ;
2022-09-01 13:05:49 +02:00
}
2023-03-10 13:40:04 +01:00
}
private void CleanupOnClose ( )
{
2022-10-02 12:40:55 +02:00
// If loading, abort. Wait for confirmation of abort before continuing the unloading.
2023-03-10 13:40:04 +01:00
if ( loadMultiThreader ! = null )
2022-10-02 12:40:55 +02:00
{
2023-03-10 13:40:04 +01:00
loadMultiThreader . AbortThreadedOperation ( 5000 ) ;
2022-10-02 12:40:55 +02:00
}
2022-09-01 13:05:49 +02:00
// Restore default icons, then dispose custom ones.
// Form dispose should take care of the default ones.
LoadNewIcon ( mapToolStripButton , null , null , 0 ) ;
LoadNewIcon ( smudgeToolStripButton , null , null , 1 ) ;
LoadNewIcon ( overlayToolStripButton , null , null , 2 ) ;
LoadNewIcon ( terrainToolStripButton , null , null , 3 ) ;
LoadNewIcon ( infantryToolStripButton , null , null , 4 ) ;
LoadNewIcon ( unitToolStripButton , null , null , 5 ) ;
LoadNewIcon ( buildingToolStripButton , null , null , 6 ) ;
LoadNewIcon ( resourcesToolStripButton , null , null , 7 ) ;
LoadNewIcon ( wallsToolStripButton , null , null , 8 ) ;
LoadNewIcon ( waypointsToolStripButton , null , null , 9 ) ;
LoadNewIcon ( cellTriggersToolStripButton , null , null , 10 ) ;
List < Bitmap > toDispose = new List < Bitmap > ( ) ;
2023-06-27 11:07:57 +02:00
foreach ( string key in theaterIcons . Keys )
2022-09-01 13:05:49 +02:00
{
toDispose . Add ( theaterIcons [ key ] ) ;
}
theaterIcons . Clear ( ) ;
foreach ( Bitmap bm in toDispose )
{
try
{
bm . Dispose ( ) ;
}
catch
{
// Ignore
}
}
2020-09-11 23:46:04 +03:00
}
2023-03-10 13:40:04 +01:00
/// <summary>
/// Returns false if the action in progress should be considered aborted.
/// </summary>
/// <param name="nextAction">Action to perform after the check. If this is after the save, the function will still return false.</param>ormcl
/// <param name="onlyAfterSave">Only perform nextAction after a save operation, not when the user pressed "no".</param>
/// <returns>false if the action was aborted.</returns>
private bool PromptSaveMap ( Action nextAction , bool onlyAfterSave )
2020-09-11 23:46:04 +03:00
{
2023-04-03 21:17:00 +02:00
#if ! DEVELOPER
if ( loadedFileType = = FileType . PGM | | loadedFileType = = FileType . MEG )
{
return true ;
}
#endif
2020-09-11 23:46:04 +03:00
if ( plugin ? . Dirty ? ? false )
{
var message = string . IsNullOrEmpty ( filename ) ? "Save new map?" : string . Format ( "Save map '{0}'?" , filename ) ;
var result = MessageBox . Show ( message , "Save" , MessageBoxButtons . YesNoCancel , MessageBoxIcon . Question ) ;
switch ( result )
{
case DialogResult . Yes :
{
2023-06-21 12:18:49 +02:00
if ( ! this . DoValidate ( ) )
2022-08-22 11:22:55 +02:00
{
return false ;
}
2020-09-11 23:46:04 +03:00
if ( string . IsNullOrEmpty ( filename ) )
{
2023-06-21 12:18:49 +02:00
SaveAsAction ( nextAction , true ) ;
2020-09-11 23:46:04 +03:00
}
else
{
2023-08-03 18:56:02 +02:00
SaveAction ( false , nextAction , true , false ) ;
2020-09-11 23:46:04 +03:00
}
2023-03-10 13:40:04 +01:00
// Cancel current operation, since stuff after multithreading will take care of the operation.
return false ;
2020-09-11 23:46:04 +03:00
}
case DialogResult . No :
break ;
case DialogResult . Cancel :
2023-03-10 13:40:04 +01:00
return false ;
2020-09-11 23:46:04 +03:00
}
2023-06-09 11:44:39 +02:00
}
2023-03-10 13:40:04 +01:00
if ( ! onlyAfterSave & & nextAction ! = null )
{
nextAction ( ) ;
2020-09-11 23:46:04 +03:00
}
2023-03-10 13:40:04 +01:00
return true ;
2020-09-11 23:46:04 +03:00
}
2022-08-22 11:22:55 +02:00
public void UpdateStatus ( )
{
SetTitle ( ) ;
}
2022-09-01 13:05:49 +02:00
private void LoadIcons ( IGamePlugin plugin )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
TheaterType theater = plugin . Map . Theater ;
string th = theater . Name ;
TemplateType template = plugin . Map . TemplateTypes . Where ( tt = > tt . ExistsInTheater & & ( tt . Flag & TemplateTypeFlag . Clear ) ! = TemplateTypeFlag . Clear
& & tt . IconWidth = = 1 & & tt . IconHeight = = 1 ) //&& (tt.Theaters == null || tt.Theaters.Contains(th)))
2023-02-24 22:08:01 +01:00
. OrderBy ( tt = > tt . Name ) . FirstOrDefault ( ) ;
2022-09-01 13:05:49 +02:00
Tile templateTile = null ;
2023-02-24 22:08:01 +01:00
if ( template ! = null )
{
2023-06-14 20:46:54 +02:00
Globals . TheTilesetManager . GetTileData ( template . Name , template . GetIconIndex ( template . GetFirstValidIcon ( ) ) , out templateTile ) ;
2023-02-24 22:08:01 +01:00
}
// For the following, check if the thumbnail was initialised.
SmudgeType smudge = plugin . Map . SmudgeTypes . Where ( sm = > ! sm . IsAutoBib & & sm . Icons = = 1 & & sm . Size . Width = = 1 & & sm . Size . Height = = 1 & & sm . Thumbnail ! = null
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
& & ( ! Globals . FilterTheaterObjects | | sm . ExistsInTheater ) )
2023-02-24 22:08:01 +01:00
. OrderBy ( sm = > sm . ID ) . FirstOrDefault ( ) ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
if ( smudge = = null )
{
smudge = plugin . Map . SmudgeTypes . Where ( sm = > ! sm . IsAutoBib & & sm . Size . Width = = 1 & & sm . Size . Height = = 1 & & sm . Thumbnail ! = null
& & ( ! Globals . FilterTheaterObjects | | sm . ExistsInTheater ) )
. OrderBy ( sm = > sm . ID ) . FirstOrDefault ( ) ;
if ( smudge = = null )
{
smudge = plugin . Map . SmudgeTypes . Where ( sm = > ! sm . IsAutoBib & & sm . Thumbnail ! = null
& & ( ! Globals . FilterTheaterObjects | | sm . ExistsInTheater ) )
. OrderByDescending ( sm = > sm . ID ) . FirstOrDefault ( ) ;
}
}
2023-03-18 01:42:43 +01:00
OverlayType overlay = plugin . Map . OverlayTypes . Where ( ov = > ( ov . Flag & OverlayTypeFlag . Crate ) ! = OverlayTypeFlag . None & & ov . Thumbnail ! = null
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
& & ( ! Globals . FilterTheaterObjects | | ov . ExistsInTheater ) )
2023-02-24 22:08:01 +01:00
. OrderBy ( ov = > ov . ID ) . FirstOrDefault ( ) ;
2022-10-02 12:40:55 +02:00
if ( overlay = = null )
{
2023-02-24 22:08:01 +01:00
overlay = plugin . Map . OverlayTypes . Where ( ov = > ( ov . Flag & OverlayTypeFlag . Flag ) = = OverlayTypeFlag . Flag & & ov . Thumbnail ! = null
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
& & ( ! Globals . FilterTheaterObjects | | ov . ExistsInTheater ) )
2023-02-24 22:08:01 +01:00
. OrderBy ( ov = > ov . ID ) . FirstOrDefault ( ) ;
2022-09-25 12:11:59 +02:00
}
2023-02-24 22:08:01 +01:00
TerrainType terrain = plugin . Map . TerrainTypes . Where ( tr = > tr . Thumbnail ! = null & &
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
( ! Globals . FilterTheaterObjects | | tr . Theaters = = null | | tr . Theaters . Contains ( th ) ) )
2023-02-24 22:08:01 +01:00
. OrderBy ( tr = > tr . ID ) . FirstOrDefault ( ) ;
2022-09-01 13:05:49 +02:00
InfantryType infantry = plugin . Map . InfantryTypes . FirstOrDefault ( ) ;
UnitType unit = plugin . Map . UnitTypes . FirstOrDefault ( ) ;
2022-09-08 09:55:29 +02:00
BuildingType building = plugin . Map . BuildingTypes . Where ( bl = > bl . Size . Width = = 2 & & bl . Size . Height = = 2
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
& & ( ! Globals . FilterTheaterObjects | | ! bl . IsTheaterDependent | | bl . ExistsInTheater ) ) . OrderBy ( bl = > bl . ID ) . FirstOrDefault ( ) ;
2022-09-08 09:55:29 +02:00
OverlayType resource = plugin . Map . OverlayTypes . Where ( ov = > ( ov . Flag & OverlayTypeFlag . TiberiumOrGold ) = = OverlayTypeFlag . TiberiumOrGold
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
& & ( ! Globals . FilterTheaterObjects | | ov . ExistsInTheater ) ) . OrderBy ( ov = > ov . ID ) . FirstOrDefault ( ) ;
2022-09-08 09:55:29 +02:00
OverlayType wall = plugin . Map . OverlayTypes . Where ( ov = > ( ov . Flag & OverlayTypeFlag . Wall ) = = OverlayTypeFlag . Wall
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
& & ( ! Globals . FilterTheaterObjects | | ov . ExistsInTheater ) ) . OrderBy ( ov = > ov . ID ) . FirstOrDefault ( ) ;
2023-06-14 20:46:54 +02:00
bool gotBeacon = Globals . TheTilesetManager . GetTileData ( "beacon" , 0 , out Tile waypoint ) ;
2022-11-27 20:48:33 +01:00
if ( ! gotBeacon )
{
// Beacon only exists in rematered graphics. Get fallback.
2023-06-16 21:46:44 +02:00
int icn = plugin . GameType = = GameType . RedAlert ? 15 : 12 ;
Globals . TheTilesetManager . GetTileData ( "mouse" , icn , out waypoint ) ;
}
Globals . TheTilesetManager . GetTileData ( "mine.shp" , 3 , out Tile cellTrigger ) ;
if ( cellTrigger = = null )
{
Globals . TheTilesetManager . GetTileData ( "mine" , 3 , out cellTrigger ) ;
2022-11-27 20:48:33 +01:00
}
2022-09-01 13:05:49 +02:00
LoadNewIcon ( mapToolStripButton , templateTile ? . Image , plugin , 0 ) ;
LoadNewIcon ( smudgeToolStripButton , smudge ? . Thumbnail , plugin , 1 ) ;
2023-02-24 22:08:01 +01:00
//LoadNewIcon(overlayToolStripButton, overlayTile?.Image, plugin, 2);
LoadNewIcon ( overlayToolStripButton , overlay ? . Thumbnail , plugin , 2 ) ;
2022-09-01 13:05:49 +02:00
LoadNewIcon ( terrainToolStripButton , terrain ? . Thumbnail , plugin , 3 ) ;
LoadNewIcon ( infantryToolStripButton , infantry ? . Thumbnail , plugin , 4 ) ;
LoadNewIcon ( unitToolStripButton , unit ? . Thumbnail , plugin , 5 ) ;
LoadNewIcon ( buildingToolStripButton , building ? . Thumbnail , plugin , 6 ) ;
LoadNewIcon ( resourcesToolStripButton , resource ? . Thumbnail , plugin , 7 ) ;
LoadNewIcon ( wallsToolStripButton , wall ? . Thumbnail , plugin , 8 ) ;
LoadNewIcon ( waypointsToolStripButton , waypoint ? . Image , plugin , 9 ) ;
LoadNewIcon ( cellTriggersToolStripButton , cellTrigger ? . Image , plugin , 10 ) ;
2023-06-14 20:46:54 +02:00
if ( Globals . TheTilesetManager is TilesetManager tsm )
2022-09-19 12:23:44 +02:00
{
2023-06-27 11:07:57 +02:00
// The Texture manager returns a clone of its own cached image. The Tileset manager caches those clones again,
// and is responsible for their cleanup, but if we use it directly it needs to be disposed.
// Alt: @"DATA\ART\TEXTURES\SRGB\ICON_IONCANNON_15.DDS
// Chronosphere cursor from TEXTURES_SRGB.MEG
2023-06-14 20:46:54 +02:00
using ( Bitmap select = tsm . TextureManager . GetTexture ( @"DATA\ART\TEXTURES\SRGB\ICON_SELECT_GREEN_04.DDS" , null , false ) . Item1 )
{
LoadNewIcon ( selectToolStripButton , select , plugin , 11 , false ) ;
}
2022-09-19 12:23:44 +02:00
}
2023-06-27 11:07:57 +02:00
else if ( Globals . UseClassicFiles )
{
if ( plugin . GameType = = GameType . TiberianDawn | | plugin . GameType = = GameType . SoleSurvivor )
{
// Ion Cannon cursor
if ( Globals . TheTilesetManager . GetTileData ( "mouse" , 118 , out Tile tile ) & & tile ! = null & & tile . Image ! = null )
{
LoadNewIcon ( selectToolStripButton , tile . Image , plugin , 11 , false ) ;
}
}
else if ( plugin . GameType = = GameType . RedAlert )
{
// Chronosphere cursor
if ( Globals . TheTilesetManager . GetTileData ( "mouse" , 101 , out Tile tile ) & & tile ! = null & & tile . Image ! = null )
{
LoadNewIcon ( selectToolStripButton , tile . Image , plugin , 11 , false ) ;
}
}
}
2022-09-01 13:05:49 +02:00
}
private void LoadNewIcon ( ViewToolStripButton button , Bitmap image , IGamePlugin plugin , int index )
2022-09-19 12:23:44 +02:00
{
LoadNewIcon ( button , image , plugin , index , true ) ;
}
private void LoadNewIcon ( ViewToolStripButton button , Bitmap image , IGamePlugin plugin , int index , bool crop )
2022-09-01 13:05:49 +02:00
{
2023-02-24 22:08:01 +01:00
if ( button . Tag = = null & & button . Image ! = null )
{
// Backup default image
button . Tag = button . Image ;
}
2022-09-01 13:05:49 +02:00
if ( image = = null | | plugin = = null )
{
if ( button . Tag is Image img )
{
button . Image = img ;
}
return ;
}
2023-06-27 11:07:57 +02:00
string id = ( ( int ) plugin . GameType ) + "_"
+ Enumerable . Range ( 0 , plugin . Map . TheaterTypes . Count ) . FirstOrDefault ( i = > plugin . Map . TheaterTypes [ i ] . ID . Equals ( plugin . Map . Theater . ID ) )
+ "_" + index ;
2022-09-01 13:05:49 +02:00
if ( theaterIcons . TryGetValue ( id , out Bitmap bm ) )
{
button . Image = bm ;
}
else
{
2023-03-12 02:40:33 +01:00
Rectangle opaqueBounds = crop ? ImageUtils . CalculateOpaqueBounds ( image ) : new Rectangle ( 0 , 0 , image . Width , image . Height ) ;
2023-02-24 22:08:01 +01:00
if ( opaqueBounds . IsEmpty )
{
if ( button . Tag is Image tagImg )
{
button . Image = tagImg ;
}
return ;
}
2022-09-01 13:05:49 +02:00
Bitmap img = image . FitToBoundingBox ( opaqueBounds , 24 , 24 , Color . Transparent ) ;
theaterIcons [ id ] = img ;
button . Image = img ;
}
}
2022-11-18 15:18:52 +01:00
private void mapPanel_PostRender ( Object sender , RenderEventArgs e )
{
2022-12-28 12:35:15 +01:00
// Only clear this after all rendering is complete.
2023-03-10 13:40:04 +01:00
if ( ! loadMultiThreader . IsExecuting & & ! saveMultiThreader . IsExecuting )
2022-11-18 15:18:52 +01:00
{
2023-03-10 13:40:04 +01:00
SimpleMultiThreading . RemoveBusyLabel ( this ) ;
2023-03-26 18:40:14 +02:00
bool performJump = false ;
lock ( jumpToBounds_lock )
{
if ( jumpToBounds )
{
jumpToBounds = false ;
performJump = true ;
}
}
if ( performJump )
2023-03-19 00:06:21 +01:00
{
if ( plugin ! = null & & plugin . Map ! = null & & mapPanel . MapImage ! = null )
{
Rectangle rect = plugin . Map . Bounds ;
2023-03-26 18:40:14 +02:00
rect . Inflate ( 1 , 1 ) ;
2023-06-28 01:00:35 +02:00
if ( plugin . Map . Metrics . Bounds = = rect )
{
mapPanel . Zoom = 1.0 ;
}
else
2023-03-26 18:40:14 +02:00
{
mapPanel . JumpToPosition ( plugin . Map . Metrics , rect , true ) ;
}
2023-03-19 00:06:21 +01:00
}
}
2022-11-18 15:18:52 +01:00
}
}
2020-09-11 23:46:04 +03:00
}
}