Editor/Tool/ArchFixtureRoles.cs

Editor-side role interfaces and static facades for architectural fixture tools. Defines interfaces for ladders, balconies, and wall modifications, and provides static helper classes that locate a single role implementation via ArchRoles.One<T>() which constructs concrete role types.

Reflection
using System.Collections.Generic;

namespace Sunless.Architecture;

// What the fixture tool authors that it does not own. Each role previews its own draft and places it, because the
// shape a preview draws belongs to the role and the fixture tool has no business resolving one. An unanswered role
// is a fixture that simply cannot be stood, rather than an error.
public interface IArchLadders
{
	bool Preview( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchLadderPart draft );

	ArchLadderPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchLadderPart draft, out ArchBuilding host );

	// Handed the buildings, because a recipe hangs a ladder on a building not yet filed in the plan.
	ArchLadderPart Place( IEnumerable<ArchBuilding> buildings, ArchPlan plan, ArchKit kit, Vector2 point, ArchLadderPart draft, out ArchBuilding host );
}

public interface IArchBalconies
{
	bool Preview( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchBalconyPart draft );

	ArchBalconyPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchBalconyPart draft, out ArchBuilding host );

	void Remove( ArchPlan plan, ArchBuilding building, ArchBalconyPart balcony );
}

// What a recess or a proud pier takes out of a wall, drawn by the module that cuts it - a block is a stack of slabs
// on either face, not a box, so the role draws its own ghost rather than handing back an envelope.
public interface IArchWallMods
{
	bool Preview( ArchWallModPart part, ArchWall wall, ArchKit kit, float wallHeight, float floor );
}

public static class ArchLadders
{
	static IArchLadders Load() => ArchRoles.One<IArchLadders>();

	public static bool Preview( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchLadderPart draft )
	{
		return Load()?.Preview( plan, kit, at, draft ) == true;
	}

	public static ArchLadderPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchLadderPart draft, out ArchBuilding host )
	{
		if ( Load() is not { } ladders )
		{
			host = null;

			return null;
		}

		return ladders.Place( plan, kit, at, draft, out host );
	}

	public static ArchLadderPart Place( IEnumerable<ArchBuilding> buildings, ArchPlan plan, ArchKit kit, Vector2 point, ArchLadderPart draft, out ArchBuilding host )
	{
		if ( Load() is not { } ladders )
		{
			host = null;

			return null;
		}

		return ladders.Place( buildings, plan, kit, point, draft, out host );
	}
}

public static class ArchBalconies
{
	static IArchBalconies Load() => ArchRoles.One<IArchBalconies>();

	public static bool Preview( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchBalconyPart draft )
	{
		return Load()?.Preview( plan, kit, at, draft ) == true;
	}

	public static ArchBalconyPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchBalconyPart draft, out ArchBuilding host )
	{
		if ( Load() is not { } balconies )
		{
			host = null;

			return null;
		}

		return balconies.Place( plan, kit, at, draft, out host );
	}

	public static void Remove( ArchPlan plan, ArchBuilding building, ArchBalconyPart balcony )
	{
		Load()?.Remove( plan, building, balcony );
	}
}

public static class ArchWallModBlocks
{
	public static bool Preview( ArchWallModPart part, ArchWall wall, ArchKit kit, float wallHeight, float floor )
	{
		return ArchRoles.One<IArchWallMods>() is { } mods && mods.Preview( part, wall, kit, wallHeight, floor );
	}
}

// The single-implementation half of ArchAnswers, for roles a caller reaches with no table of its own. A role
// nothing answers hands back null, which every caller falls back from rather than erroring.
public static class ArchRoles
{
	public static T One<T>() where T : class => Answering( typeof( T ) ) as T;

	static object Answering( Type role )
	{
		if ( role == typeof( IArchWallForms ) ) return new ArchWallFormsRole();
		if ( role == typeof( IArchWallMods ) ) return new ArchWallModRole();
		if ( role == typeof( IArchBalconies ) ) return new ArchBalconyRole();
		if ( role == typeof( IArchLadders ) ) return new ArchLadderRole();
		if ( role == typeof( IArchFlightBuilder ) ) return new ArchFlightBuilder();
		if ( role == typeof( IArchWellEdges ) ) return new ArchStairWellEdges();
		if ( role == typeof( IArchPiercer ) ) return new ArchStairPiercer();
		if ( role == typeof( IArchHostCarving ) ) return new ArchStairHostCarving();
		if ( role == typeof( IArchExteriorStairs ) ) return new ArchExteriorStairAuthoring();

		return null;
	}
}