Editor/Wall/ArchWallDamage.cs

Editor utility that generates wall corner recess modifiers from masonry damage cuts. It iterates damage volumes affecting a wall, computes brick/course parameters and vertical ranges, and returns a list of ArchWallModShape entries describing carved reveals at wall ends.

File Access
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// The recesses a masonry cut leaves at a wall's own corners, expressed as ordinary wall modifiers so the generator
// carves them with the same pass it carves an authored indent with.
public static class ArchWallDamage
{
	public static List<ArchWallModShape> CornerRecesses(
		ArchPlan plan,
		ArchKit kit,
		ArchBuilding building,
		ArchRoom room,
		ArchWall wall,
		float edgeFrom,
		float edgeTo,
		float height,
		float thickness )
	{
		var recesses = new List<ArchWallModShape>();

		foreach ( var (cut, volume) in ArchCut.Damage( plan, room.Floor, kit, room.BaseHeight, room.BaseHeight + height, building.Id, ArchCutAffects.Walls, wall.Id ) )
		{
			if ( cut.ResolvedDamage != ArchDamageKind.Masonry || !cut.MasonryCarves )
			{
				continue;
			}

			foreach ( var (point, along, direction) in new[]
			{
				(wall.Start, edgeFrom, -1f),
				(wall.End, edgeTo, 1f)
			} )
			{
				var brick = MathF.Max( 2f, cut.DamageCellWidth );
				var course = MathF.Max( 2f, cut.DamageCellLength );
				var corner = ArchFootprint.Rect( point - new Vector2( brick * 2f, brick * 2f ), point + new Vector2( brick * 2f, brick * 2f ) );

				if ( !ArchFootprint.Overlaps( volume.Footprint, corner ) )
				{
					continue;
				}

				var bottom = Math.Clamp( volume.Floor.At( point ) - room.BaseHeight, 0f, height );
				var top = Math.Clamp( volume.Ceiling.At( point ) - room.BaseHeight, 0f, height );
				var first = (int)MathF.Floor( bottom / course );
				var last = (int)MathF.Ceiling( top / course );

				for ( var z = first; z < last; z++ )
				{
					var state = ArchDamage.CourseAt( cut, z, direction );

					if ( !ArchDamage.KeepsCourse( cut, wall.Id, z ) )
					{
						continue;
					}

					var low = MathF.Max( bottom, z * course );
					var high = MathF.Min( top, (z + 1) * course );
					var brickSpan = state switch
					{
						ArchDamage.MasonryCourse.Header => MathF.Max( course * 1.25f, brick * 0.35f ),
						_ => brick
					};
					var chipped = course * (0.35f + ArchBarrierShape.Noise( ArchDamage.Seed( cut ), wall.Id ^ z * 83492791, ArchDamage.StateSalt ) * 0.45f );
					var span = brickSpan + chipped;
					var from = direction < 0f ? along : along - span;
					var to = direction < 0f ? along + span : along;

					recesses.Add( new ArchWallModShape
					{
						Part = new ArchWallModPart
						{
							Name = "Masonry Reveal",
							Kind = ArchWallMod.Indent,
							Side = ArchWallSide.Outside,
							Field = ArchSurface.Reveal,
							Palette = cut.Palette
						},
						Carves = true,
						Sealed = true,
						Outside = true,
						From = from,
						To = to,
						Bottom = low,
						Top = high,
						Depth = MathF.Min( MathF.Max( 0.25f, cut.DamageDepth ), MathF.Max( 0.25f, thickness - 0.5f ) )
					} );
				}
			}
		}

		return recesses;
	}
}