#region ================== Copyright (c) 2016 Boris Iwanski /* * Copyright (c) 2016 Boris Iwanski https://github.com/biwa/automapmode * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.Editing; #endregion namespace CodeImp.DoomBuilder.AutomapMode { [EditMode(DisplayName = "Automap Mode", SwitchAction = "automapmode", // Action name used to switch to this mode ButtonImage = "automap.png", // Image resource name for the button ButtonOrder = int.MinValue + 503, // Position of the button (lower is more to the bottom) ButtonGroup = "000_editing", UseByDefault = true)] public class AutomapMode : ClassicMode { #region ================== Enums internal enum ColorPreset { DOOM, HEXEN, STRIFE, } #endregion #region ================== Constants private const float LINE_LENGTH_SCALER = 0.001f; //mxd #endregion #region ================== Variables private CustomPresentation automappresentation; private List validlinedefs; private HashSet secretsectors; //mxd // Highlighted items private Linedef highlightedLine; private Sector highlightedSector; //mxd. UI private MenusForm menusform; //mxd. Colors private PixelColor ColorSingleSided; private PixelColor ColorSecret; private PixelColor ColorFloorDiff; private PixelColor ColorCeilDiff; private PixelColor ColorMatchingHeight; private PixelColor ColorHiddenFlag; private PixelColor ColorInvisible; private PixelColor ColorBackground; // Options private bool invertLineVisibility; // CTRL to toggle private bool editSectors; // SHIFT to toggle #endregion #region ================== Properties public override object HighlightedObject { get { if(highlightedLine != null) return highlightedLine; else return highlightedSector; } } #endregion #region ================== Constructor / Disposer //mxd public AutomapMode() { // Create and setup menu menusform = new MenusForm(); menusform.ShowHiddenLines = General.Settings.ReadPluginSetting("automapmode.showhiddenlines", false); menusform.ShowSecretSectors = General.Settings.ReadPluginSetting("automapmode.showsecretsectors", false); menusform.ShowLocks = General.Settings.ReadPluginSetting("automapmode.showlocks", true); menusform.ShowTextures = General.Settings.ReadPluginSetting("automapmode.showtextures", true); menusform.ColorPreset = (ColorPreset)General.Settings.ReadPluginSetting("automapmode.colorpreset", (int)ColorPreset.DOOM); // Handle events menusform.OnShowHiddenLinesChanged += delegate { UpdateValidLinedefs(); General.Interface.RedrawDisplay(); }; menusform.OnShowSecretSectorsChanged += delegate { General.Interface.RedrawDisplay(); }; menusform.OnShowLocksChanged += delegate { General.Interface.RedrawDisplay(); }; menusform.OnShowTexturesChanged += delegate { General.Interface.RedrawDisplay(); }; menusform.OnColorPresetChanged += delegate { ApplyColorPreset(menusform.ColorPreset); General.Interface.RedrawDisplay(); }; // Apply color preset ApplyColorPreset(menusform.ColorPreset); } #endregion #region ================== Methods // Update the current highlight private void UpdateHighlight() { if (EditSectors()) { // Get the nearest sector to the cursor; don't factor in the // highlight range since we really just want to capture // whichever sector is under the cursor. Sector s = General.Map.Map.GetSectorByCoordinates(mousemappos); if (s != highlightedSector) HighlightSector(s); } else { // Find the nearest linedef within highlight range Linedef l = MapSet.NearestLinedefRange(validlinedefs, mousemappos, BuilderPlug.Me.HighlightRange / renderer.Scale); // Highlight if not the same if (l != highlightedLine) HighlightLine(l); } } // This highlights a new line private void HighlightLine(Linedef l) { // Update display if(renderer.StartPlotter(false)) { // Undraw previous highlight if((highlightedLine != null) && !highlightedLine.IsDisposed) { PixelColor c = LinedefIsValid(highlightedLine) ? DetermineLinedefColor(highlightedLine) : PixelColor.Transparent; renderer.PlotLine(highlightedLine.Start.Position, highlightedLine.End.Position, c, LINE_LENGTH_SCALER); } // Set new highlight highlightedLine = l; // Render highlighted item if((highlightedLine != null) && !highlightedLine.IsDisposed && LinedefIsValid(highlightedLine)) { renderer.PlotLine(highlightedLine.Start.Position, highlightedLine.End.Position, General.Colors.InfoLine, LINE_LENGTH_SCALER); } // Done renderer.Finish(); renderer.Present(); } // Show highlight info if((highlightedLine != null) && !highlightedLine.IsDisposed) General.Interface.ShowLinedefInfo(highlightedLine); else General.Interface.HideInfo(); } // This highlights a new sector private void HighlightSector(Sector sector) { // Update display if (renderer.StartPlotter(false)) { // Undraw previous highlight if ((highlightedSector != null) && !highlightedSector.IsDisposed) { foreach(Sidedef sd in highlightedSector.Sidedefs) { if ((sd.Line != null) && !sd.Line.IsDisposed) { PixelColor c = LinedefIsValid(sd.Line) ? DetermineLinedefColor(sd.Line) : PixelColor.Transparent; renderer.PlotLine(sd.Line.Start.Position, sd.Line.End.Position, c, LINE_LENGTH_SCALER); } } } // Set new highlight highlightedSector = sector; // Render highlighted sector's lines if ((highlightedSector != null) && !highlightedSector.IsDisposed) foreach (Sidedef sd in highlightedSector.Sidedefs) if ((sd.Line != null) && !sd.Line.IsDisposed) renderer.PlotLine(sd.Line.Start.Position, sd.Line.End.Position, General.Colors.Highlight, LINE_LENGTH_SCALER); // Done renderer.Finish(); renderer.Present(); } // Show highlight info if ((highlightedSector != null) && !highlightedSector.IsDisposed) General.Interface.ShowSectorInfo(highlightedSector); else General.Interface.HideInfo(); } //mxd internal void UpdateValidLinedefs() { validlinedefs = new List(); foreach(Linedef ld in General.Map.Map.Linedefs) if(LinedefIsValid(ld)) validlinedefs.Add(ld); } //mxd internal void UpdateSecretSectors() { secretsectors = new HashSet(); foreach(Sector s in General.Map.Map.Sectors) if(SectorIsSecret(s)) secretsectors.Add(s); } private PixelColor DetermineLinedefColor(Linedef ld) { //mxd if(menusform.ShowLocks) { PixelColor lockcolor = new PixelColor(); if(GetLockColor(ld, ref lockcolor)) return lockcolor; } //mxd if(menusform.ShowSecretSectors && (ld.Front != null && secretsectors.Contains(ld.Front.Sector) || ld.Back != null && secretsectors.Contains(ld.Back.Sector))) return ColorSecret; if(ld.IsFlagSet(BuilderPlug.Me.HiddenFlag)) return ColorHiddenFlag; if(ld.Back == null || ld.Front == null || ld.IsFlagSet(BuilderPlug.Me.SecretFlag)) return ColorSingleSided; if(ld.Front.Sector.FloorHeight != ld.Back.Sector.FloorHeight) return ColorFloorDiff; if(ld.Front.Sector.CeilHeight != ld.Back.Sector.CeilHeight) return ColorCeilDiff; if(ld.Front.Sector.CeilHeight == ld.Back.Sector.CeilHeight && ld.Front.Sector.FloorHeight == ld.Back.Sector.FloorHeight) return ColorMatchingHeight; if(menusform.ShowHiddenLines ^ invertLineVisibility) return ColorInvisible; return new PixelColor(255, 255, 255, 255); } private bool LinedefIsValid(Linedef ld) { if(menusform.ShowHiddenLines ^ invertLineVisibility) return true; if(ld.IsFlagSet(BuilderPlug.Me.HiddenFlag)) return false; if(ld.Back == null || ld.Front == null || ld.IsFlagSet(BuilderPlug.Me.SecretFlag)) return true; if(ld.Back != null && ld.Front != null && (ld.Front.Sector.FloorHeight != ld.Back.Sector.FloorHeight || ld.Front.Sector.CeilHeight != ld.Back.Sector.CeilHeight)) return true; return false; } private bool SectorIsVisible(Sector s) { return(s != null && !s.IsFlagSet("hidden")); } private bool ShowTextures() { return menusform.ShowTextures || EditSectors(); } private bool EditSectors() { return editSectors && General.Map.UDMF; } //mxd private static bool SectorIsSecret(Sector s) { SectorEffectData data = General.Map.Config.GetSectorEffectData(s.Effect); if(General.Map.DOOM) { // Sector is secret when it's Special is 9 or it has generalized flag 128 if(data.Effect == 9 || data.GeneralizedBits.Contains(128)) return true; } else { //Hexen/UDMF: sector is secret when it has generalized flag 1024 if(data.GeneralizedBits.Contains(1024)) return true; } return false; } //mxd private static bool GetLockColor(Linedef l, ref PixelColor lockcolor) { int locknum = 0; // Check locknumber property if(General.Map.UDMF) { locknum = UniFields.GetInteger(l.Fields, "locknumber"); } // Check action if(locknum == 0 && l.Action != 0 && General.Map.Data.LockableActions.ContainsKey(l.Action)) { locknum = l.Args[General.Map.Data.LockableActions[l.Action]]; } if(locknum != 0 && General.Map.Data.LockColors.ContainsKey(locknum)) { lockcolor = General.Map.Data.LockColors[locknum]; return true; } // No dice return false; } //mxd private void ApplyColorPreset(ColorPreset preset) { switch(preset) { case ColorPreset.DOOM: ColorSingleSided = new PixelColor(255, 252, 0, 0); ColorSecret = new PixelColor(255, 255, 0, 255); ColorFloorDiff = new PixelColor(255, 188, 120, 72); ColorCeilDiff = new PixelColor(255, 252, 252, 0); ColorHiddenFlag = new PixelColor(255, 192, 192, 192); ColorInvisible = new PixelColor(255, 192, 192, 192); ColorMatchingHeight = new PixelColor(255, 108, 108, 108); ColorBackground = new PixelColor(255, 0, 0, 0); break; case ColorPreset.HEXEN: ColorSingleSided = new PixelColor(255, 89, 64, 27); ColorSecret = new PixelColor(255, 255, 0, 255); ColorFloorDiff = new PixelColor(255, 208, 176, 133); ColorCeilDiff = new PixelColor(255, 103, 59, 31); ColorHiddenFlag = new PixelColor(255, 192, 192, 192); ColorInvisible = new PixelColor(255, 108, 108, 108); ColorMatchingHeight = new PixelColor(255, 108, 108, 108); ColorBackground = new PixelColor(255, 163, 129, 84); break; case ColorPreset.STRIFE: ColorSingleSided = new PixelColor(255, 199, 195, 195); ColorSecret = new PixelColor(255, 255, 0, 255); ColorFloorDiff = new PixelColor(255, 55, 59, 91); ColorCeilDiff = new PixelColor(255, 108, 108, 108); ColorHiddenFlag = new PixelColor(255, 0, 87, 130); ColorInvisible = new PixelColor(255, 192, 192, 192); ColorMatchingHeight = new PixelColor(255, 112, 112, 160); ColorBackground = new PixelColor(255, 0, 0, 0); break; } } #endregion #region ================== Events public override void OnHelp() { General.ShowHelp("/gzdb/features/classic_modes/mode_automap.html"); } // Cancel mode public override void OnCancel() { base.OnCancel(); // Return to this mode General.Editing.ChangeMode(new AutomapMode()); } // Mode engages public override void OnEngage() { base.OnEngage(); renderer.DrawMapCenter = false; //mxd // Automap presentation; now draws surfaces for textured mode support, // but the surfaces are covered up with a background layer. automappresentation = new CustomPresentation(); automappresentation.AddLayer(new PresentLayer(RendererLayer.Surface, BlendingMode.Mask)); automappresentation.AddLayer(new PresentLayer(RendererLayer.Overlay, BlendingMode.Mask)); automappresentation.AddLayer(new PresentLayer(RendererLayer.Grid, BlendingMode.Mask)); automappresentation.AddLayer(new PresentLayer(RendererLayer.Geometry, BlendingMode.Alpha, 1f, true)); automappresentation.SkipHiddenSectors = true; renderer.SetPresentation(automappresentation); UpdateValidLinedefs(); UpdateSecretSectors(); //mxd //mxd. Show UI menusform.Register(); } // Mode disengages public override void OnDisengage() { base.OnDisengage(); //mxd. Store settings General.Settings.WritePluginSetting("automapmode.showhiddenlines", menusform.ShowHiddenLines); General.Settings.WritePluginSetting("automapmode.showsecretsectors", menusform.ShowSecretSectors); General.Settings.WritePluginSetting("automapmode.showlocks", menusform.ShowLocks); General.Settings.WritePluginSetting("automapmode.showtextures", menusform.ShowTextures); General.Settings.WritePluginSetting("automapmode.colorpreset", (int)menusform.ColorPreset); //mxd. Hide UI menusform.Unregister(); // Hide highlight info General.Interface.HideInfo(); } //mxd public override void OnUndoEnd() { UpdateValidLinedefs(); UpdateSecretSectors(); base.OnUndoEnd(); } //mxd public override void OnRedoEnd() { UpdateValidLinedefs(); UpdateSecretSectors(); base.OnRedoEnd(); } // This redraws the display public override void OnRedrawDisplay() { renderer.RedrawSurface(); // Render lines if(renderer.StartPlotter(true)) { foreach(Linedef ld in General.Map.Map.Linedefs) { if(LinedefIsValid(ld)) renderer.PlotLine(ld.Start.Position, ld.End.Position, DetermineLinedefColor(ld), LINE_LENGTH_SCALER); } if((highlightedLine != null) && !highlightedLine.IsDisposed && LinedefIsValid(highlightedLine)) { renderer.PlotLine(highlightedLine.Start.Position, highlightedLine.End.Position, General.Colors.InfoLine, LINE_LENGTH_SCALER); } renderer.Finish(); } //mxd. Render background if(renderer.StartOverlay(true)) { if(!ShowTextures()) { RectangleF screenrect = new RectangleF(0, 0, General.Interface.Display.Width, General.Interface.Display.Height); renderer.RenderRectangleFilled(screenrect, ColorBackground, false); } renderer.Finish(); } renderer.Present(); } protected override void OnSelectEnd() { // Line highlighted? if((highlightedLine != null) && !highlightedLine.IsDisposed) { General.Map.UndoRedo.CreateUndo("Toggle \"Shown as 1-sided on automap\" linedef flag"); // Toggle flag highlightedLine.SetFlag(BuilderPlug.Me.SecretFlag, !highlightedLine.IsFlagSet(BuilderPlug.Me.SecretFlag)); UpdateValidLinedefs(); } // Sector highlighted? if((highlightedSector != null) && !highlightedSector.IsDisposed) { General.Map.UndoRedo.CreateUndo("Toggle \"Not shown on textured automap\" sector flag"); // Toggle flag highlightedSector.SetFlag("hidden", !highlightedSector.IsFlagSet("hidden")); // Redraw the universe General.Map.Map.Update(); General.Interface.RedrawDisplay(); // Re-highlight the sector since it gets lost after RedrawDisplay HighlightSector(highlightedSector); } base.OnSelectEnd(); } protected override void OnEditEnd() { // Line highlighted? if ((highlightedLine != null) && !highlightedLine.IsDisposed) { General.Map.UndoRedo.CreateUndo("Toggle \"Not shown on automap\" linedef flag"); // Toggle flag highlightedLine.SetFlag(BuilderPlug.Me.HiddenFlag, !highlightedLine.IsFlagSet(BuilderPlug.Me.HiddenFlag)); UpdateValidLinedefs(); General.Interface.RedrawDisplay(); } base.OnEditEnd(); } // Mouse moves public override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); // Not holding any buttons? if(e.Button == MouseButtons.None) { UpdateHighlight(); } } // Mouse leaves public override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); // Highlight nothing HighlightLine(null); HighlightSector(null); } // Keyboard input handling; toggles a couple of options public override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); UpdateOptions(); } public override void OnKeyUp(KeyEventArgs e) { base.OnKeyUp(e); UpdateOptions(); } private void UpdateOptions() { if(invertLineVisibility != General.Interface.CtrlState) { invertLineVisibility = General.Interface.CtrlState; UpdateValidLinedefs(); General.Interface.RedrawDisplay(); } if(editSectors != General.Interface.ShiftState) { editSectors = General.Interface.ShiftState; HighlightLine(null); HighlightSector(null); General.Interface.RedrawDisplay(); UpdateHighlight(); } } #endregion } }