// // Copyright 2020 Electronic Arts Inc. // // 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, // either version 3 of the License, or (at your option) any later version. // 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 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection using MobiusEditor.Controls; using MobiusEditor.Event; using MobiusEditor.Interface; using MobiusEditor.Model; using MobiusEditor.Utility; using MobiusEditor.Widgets; using System; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace MobiusEditor.Tools { public class TerrainTool : ViewTool { private readonly TypeComboBox terrainTypeComboBox; private readonly MapPanel terrainTypeMapPanel; private readonly TerrainProperties terrainProperties; private Map previewMap; protected override Map RenderMap => previewMap; private bool placementMode; private readonly Terrain mockTerrain; private Terrain selectedTerrain; private Point selectedTerrainPivot; private TerrainType selectedTerrainType; private TerrainPropertiesPopup selectedTerrainProperties; private TerrainType SelectedTerrainType { get => selectedTerrainType; set { if (selectedTerrainType != value) { if (placementMode && (selectedTerrainType != null)) { mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size)); } selectedTerrainType = value; terrainTypeComboBox.SelectedValue = selectedTerrainType; if (placementMode && (selectedTerrainType != null)) { mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size)); } mockTerrain.Type = selectedTerrainType; mockTerrain.Icon = selectedTerrainType.IsTransformable ? 22 : 0; RefreshMapPanel(); } } } public TerrainTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox terrainTypeComboBox, MapPanel terrainTypeMapPanel, TerrainProperties terrainProperties, IGamePlugin plugin, UndoRedoList url) : base(mapPanel, layers, statusLbl, plugin, url) { previewMap = map; mockTerrain = new Terrain(); mockTerrain.PropertyChanged += MockTerrain_PropertyChanged; this.mapPanel.MouseDown += MapPanel_MouseDown; this.mapPanel.MouseMove += MapPanel_MouseMove; this.mapPanel.MouseUp += MapPanel_MouseUp; this.mapPanel.MouseDoubleClick += MapPanel_MouseDoubleClick; (this.mapPanel as Control).KeyDown += TerrainTool_KeyDown; (this.mapPanel as Control).KeyUp += TerrainTool_KeyUp; this.terrainTypeComboBox = terrainTypeComboBox; this.terrainTypeComboBox.SelectedIndexChanged += TerrainTypeCombo_SelectedIndexChanged; this.terrainTypeMapPanel = terrainTypeMapPanel; this.terrainTypeMapPanel.BackColor = Color.White; this.terrainTypeMapPanel.MaxZoom = 1; this.terrainProperties = terrainProperties; this.terrainProperties.Terrain = mockTerrain; this.terrainProperties.Visible = plugin.GameType == GameType.TiberianDawn; navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged; SelectedTerrainType = terrainTypeComboBox.Types.First() as TerrainType; UpdateStatus(); } private void MapPanel_MouseDoubleClick(object sender, MouseEventArgs e) { if (Control.ModifierKeys != Keys.None) { return; } if (map.Metrics.GetCell(navigationWidget.MouseCell, out int cell)) { if (map.Technos[cell] is Terrain terrain) { selectedTerrain = null; selectedTerrainProperties?.Close(); selectedTerrainProperties = new TerrainPropertiesPopup(terrainProperties.Plugin, terrain); selectedTerrainProperties.Closed += (cs, ce) => { navigationWidget.Refresh(); }; selectedTerrainProperties.Show(mapPanel, mapPanel.PointToClient(Control.MousePosition)); UpdateStatus(); } } } private void MockTerrain_PropertyChanged(object sender, PropertyChangedEventArgs e) { RefreshMapPanel(); } private void TerrainTypeCombo_SelectedIndexChanged(object sender, EventArgs e) { SelectedTerrainType = terrainTypeComboBox.SelectedValue as TerrainType; } private void TerrainTool_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.ShiftKey) { EnterPlacementMode(); } } private void TerrainTool_KeyUp(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.ShiftKey) { ExitPlacementMode(); } } private void MapPanel_MouseDown(object sender, MouseEventArgs e) { if (placementMode) { if (e.Button == MouseButtons.Left) { AddTerrain(navigationWidget.MouseCell); } else if (e.Button == MouseButtons.Right) { RemoveTerrain(navigationWidget.MouseCell); } } else if (e.Button == MouseButtons.Left) { SelectTerrain(navigationWidget.MouseCell); } else if (e.Button == MouseButtons.Right) { PickTerrain(navigationWidget.MouseCell); } } private void MapPanel_MouseUp(object sender, MouseEventArgs e) { if (selectedTerrain != null) { selectedTerrain = null; selectedTerrainPivot = Point.Empty; UpdateStatus(); } } private void MapPanel_MouseMove(object sender, MouseEventArgs e) { if (!placementMode && (Control.ModifierKeys == Keys.Shift)) { EnterPlacementMode(); } else if (placementMode && (Control.ModifierKeys == Keys.None)) { ExitPlacementMode(); } } private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e) { if (placementMode) { if (SelectedTerrainType != null) { mapPanel.Invalidate(map, new Rectangle(e.OldCell, SelectedTerrainType.OverlapBounds.Size)); mapPanel.Invalidate(map, new Rectangle(e.NewCell, SelectedTerrainType.OverlapBounds.Size)); } } else if (selectedTerrain != null) { var oldLocation = map.Technos[selectedTerrain].Value; var newLocation = new Point(Math.Max(0, e.NewCell.X - selectedTerrainPivot.X), Math.Max(0, e.NewCell.Y - selectedTerrainPivot.Y)); mapPanel.Invalidate(map, selectedTerrain); map.Technos.Remove(selectedTerrain); if (map.Technos.Add(newLocation, selectedTerrain)) { mapPanel.Invalidate(map, selectedTerrain); } else { map.Technos.Add(oldLocation, selectedTerrain); } } } private void AddTerrain(Point location) { if (!map.Metrics.Contains(location)) { return; } if (SelectedTerrainType != null) { var terrain = mockTerrain.Clone(); if (map.Technos.Add(location, terrain)) { mapPanel.Invalidate(map, terrain); void undoAction(UndoRedoEventArgs e) { e.MapPanel.Invalidate(e.Map, location); e.Map.Technos.Remove(terrain); } void redoAction(UndoRedoEventArgs e) { e.Map.Technos.Add(location, terrain); e.MapPanel.Invalidate(e.Map, location); } url.Track(undoAction, redoAction); plugin.Dirty = true; } } } private void RemoveTerrain(Point location) { if (map.Technos[location] is Terrain terrain) { mapPanel.Invalidate(map, terrain); map.Technos.Remove(location); void undoAction(UndoRedoEventArgs e) { e.Map.Technos.Add(location, terrain); e.MapPanel.Invalidate(e.Map, location); } void redoAction(UndoRedoEventArgs e) { e.MapPanel.Invalidate(e.Map, location); e.Map.Technos.Remove(terrain); } url.Track(undoAction, redoAction); plugin.Dirty = true; } } private void EnterPlacementMode() { if (placementMode) { return; } placementMode = true; navigationWidget.MouseoverSize = Size.Empty; if (SelectedTerrainType != null) { mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size)); } UpdateStatus(); } private void ExitPlacementMode() { if (!placementMode) { return; } placementMode = false; navigationWidget.MouseoverSize = new Size(1, 1); if (SelectedTerrainType != null) { mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size)); } UpdateStatus(); } private void PickTerrain(Point location) { if (map.Metrics.GetCell(location, out int cell)) { if (map.Technos[cell] is Terrain terrain) { SelectedTerrainType = terrain.Type; mockTerrain.Trigger = terrain.Trigger; } } } private void SelectTerrain(Point location) { if (map.Metrics.GetCell(location, out int cell)) { selectedTerrain = map.Technos[cell] as Terrain; selectedTerrainPivot = (selectedTerrain != null) ? (location - (Size)map.Technos[selectedTerrain].Value) : Point.Empty; } UpdateStatus(); } private void RefreshMapPanel() { terrainTypeMapPanel.MapImage = mockTerrain.Type.Thumbnail; } private void UpdateStatus() { if (placementMode) { statusLbl.Text = "Left-Click to place terrain, Right-Click to remove terrain"; } else if (selectedTerrain != null) { statusLbl.Text = "Drag mouse to move terrain"; } else { statusLbl.Text = "Shift to enter placement mode, Left-Click drag to move terrain, Double-Click update terrain properties, Right-Click to pick terrain"; } } protected override void PreRenderMap() { base.PreRenderMap(); previewMap = map.Clone(); if (placementMode) { var location = navigationWidget.MouseCell; if (SelectedTerrainType != null) { if (previewMap.Metrics.Contains(location)) { var terrain = new Terrain { Type = SelectedTerrainType, Icon = SelectedTerrainType.IsTransformable ? 22 : 0, Tint = Color.FromArgb(128, Color.White) }; previewMap.Technos.Add(location, terrain); } } } } protected override void PostRenderMap(Graphics graphics) { base.PostRenderMap(graphics); var terrainPen = new Pen(Color.Green, 4.0f); var occupyPen = new Pen(Color.Red, 2.0f); foreach (var (topLeft, terrain) in previewMap.Technos.OfType()) { var bounds = new Rectangle(new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight), terrain.Type.RenderSize); graphics.DrawRectangle(terrainPen, bounds); for (var y = 0; y < terrain.Type.OccupyMask.GetLength(0); ++y) { for (var x = 0; x < terrain.Type.OccupyMask.GetLength(1); ++x) { if (terrain.Type.OccupyMask[y, x]) { var occupyBounds = new Rectangle( new Point((topLeft.X + x) * Globals.TileWidth, (topLeft.Y + y) * Globals.TileHeight), Globals.TileSize ); graphics.DrawRectangle(occupyPen, occupyBounds); } } } } } #region IDisposable Support private bool disposedValue = false; protected override void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { selectedTerrainProperties?.Close(); mapPanel.MouseDown -= MapPanel_MouseDown; mapPanel.MouseMove -= MapPanel_MouseMove; mapPanel.MouseUp -= MapPanel_MouseUp; mapPanel.MouseDoubleClick -= MapPanel_MouseDoubleClick; (mapPanel as Control).KeyDown -= TerrainTool_KeyDown; (mapPanel as Control).KeyUp -= TerrainTool_KeyUp; terrainTypeComboBox.SelectedIndexChanged -= TerrainTypeCombo_SelectedIndexChanged; navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged; } disposedValue = true; } base.Dispose(disposing); } #endregion } }