Editor/Carve/ArchCarveHosts.cs
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 );
}