Interfaces and helpers for carving tools in the editor. Defines IArchCarveHost (contract for things that carving tools can act on), a small value type ArchCarveDrag that records which host and drag endpoints, and static helper functions to query hosts under points, aim a drag to a host, and test containment and rise.
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// A contract, not a tool mode, so carvers ask "what am I over" - no per-tool switches.
public interface IArchCarveHost
{
int Id { get; }
string Name { get; }
int Level { get; }
float GradeHeight { get; }
float TopHeight { get; }
List<ArchStairPart> Stairs { get; }
List<Vector2> Outline();
}
// Resolved once, so the placement ghost and the commit can never disagree.
public sealed class ArchCarveDrag
{
public IArchCarveHost Host { get; init; }
public Vector2 Foot { get; init; }
public Vector2 Head { get; init; }
public bool Lands => Host is not null;
}
public static class ArchCarveHosts
{
// Every building, never the active one - ActiveBuilding() falls back to an empty placeholder.
public static IEnumerable<IArchCarveHost> On( ArchPlan plan, int level, ArchKinds table = null )
{
if ( plan is null )
{
return Enumerable.Empty<IArchCarveHost>();
}
return plan.Parts( ArchKind.Platform, table ).OfType<IArchCarveHost>().Where( host => host.Level == level );
}
// One lookup, so every tool's drag lands on the same host.
public static IArchCarveHost Under( ArchPlan plan, int level, Vector2 point, ArchKinds table = null )
{
return On( plan, level, table ).FirstOrDefault( host => host.Holds( point ) );
}
// Aimed, not literal: drawing up or down from the ground must mean the same flight.
public static ArchCarveDrag Aim( ArchPlan plan, int level, Vector2 from, Vector2 to )
{
var middle = (from + to) * 0.5f;
var kinds = ArchKinds.Load();
var host = Under( plan, level, from, kinds ) ?? Under( plan, level, to, kinds ) ?? Under( plan, level, middle, kinds );
if ( host is null )
{
return new ArchCarveDrag { Foot = from, Head = to };
}
var onFoot = host.Holds( from );
var onHead = host.Holds( to );
// One end off the host = ground, so climb away; otherwise the drag is taken as authored.
var flip = onFoot && !onHead;
return new ArchCarveDrag
{
Host = host,
Foot = flip ? to : from,
Head = flip ? from : to
};
}
public static IArchCarveHost Holding( ArchPlan plan, ArchStairPart stair )
{
return plan?.HostOf<IArchCarveHost>( stair );
}
public static bool Holds( this IArchCarveHost host, Vector2 point )
{
return ArchFootprint.Encloses( new[] { (IReadOnlyList<Vector2>)host.Outline() }, point );
}
public static float Rise( this IArchCarveHost host ) => MathF.Max( 1f, host.TopHeight - host.GradeHeight );
}