Editor/TerrainLinesRegistration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Editor;
using Editor.TerrainEditor;
using Sandbox;

namespace TerrainLines;

internal static class TerrainLinesRegistration
{
	private static bool _unsupported;

	[EditorEvent.Frame]
	private static void Attach()
	{
		if ( _unsupported || SceneViewWidget.Current?.Tools?.CurrentTool is not TerrainEditorTool parent ) return;
		if ( parent.Tools.Any( x => x is TerrainLineTool ) ) return;
		try
		{
			// Native Terrain has no registration API. Keep the two internal assumptions here.
			if ( parent.Tools is not ICollection<EditorTool> tools || tools.IsReadOnly )
				throw new InvalidOperationException( "Terrain's tool collection is no longer mutable." );
			var setter = typeof( EditorTool ).GetProperty( nameof( EditorTool.Manager ), BindingFlags.Public | BindingFlags.Instance );
			if ( setter?.GetSetMethod( true ) is null ) throw new InvalidOperationException( "EditorTool.Manager has no setter." );
			var lines = new TerrainLineTool();
			setter.SetValue( lines, parent.Manager );
			tools.Add( lines );
			foreach ( var toolbar in SceneViewWidget.Current.GetDescendants<VerticalToolbarGroup>()
				.Where( x => x.GetType().Name.Contains( "EditorSubToolBarWidget" ) ) ) toolbar.Build();
		}
		catch ( Exception e )
		{
			_unsupported = true;
			Log.Warning( $"Terrain Lines cannot attach to this editor version: {e.Message}" );
		}
	}

	[Shortcut( "tools.terrain.lines", "7", typeof( SceneViewWidget ) )]
	public static void SelectLines()
	{
		if ( SceneViewWidget.Current?.Tools?.CurrentTool is not TerrainEditorTool ) return;
		Attach();
		EditorToolManager.SetSubTool( nameof( TerrainLineTool ) );
	}

	[EditorEvent.Hotload]
	private static void AfterHotload()
	{
		_unsupported = false;
		if ( SceneViewWidget.Current?.Tools?.CurrentTool is not TerrainEditorTool parent ) return;
		if ( parent.Tools is not ICollection<EditorTool> tools || tools.IsReadOnly ) return;
		foreach ( var previous in tools.OfType<TerrainLineTool>().ToArray() )
		{
			// Native sidebars only rebuild when the tool identity changes. Recreate callbacks after hotload.
			previous.Cancel();
			bool selected = parent.CurrentTool == previous;
			var replacement = new TerrainLineTool { Settings = previous.Settings };
			typeof( EditorTool ).GetProperty( nameof( EditorTool.Manager ) ).SetValue( replacement, parent.Manager );
			tools.Remove( previous );
			tools.Add( replacement );
			if ( selected ) parent.CurrentTool = replacement;
			previous.Dispose();
		}
	}
}