namespace FoodFactory.Conveyors; using Chickensoft.Introspection; using Chickensoft.AutoInject; using Godot; using System; using System.Collections.Generic; using System.Linq; using Chickensoft.Sync.Primitives; using SJK.Functional; using SJK.Math; using FoodFactory.Items; using FoodFactory.Voxel; [Meta(typeof(IAutoNode))] [Tool] public partial class TestItemConveyor : Node, IMovementConveyor { public override void _Notification(int what) => this.Notify(what); [Dependency] public IBeltMovement MovementSystem => this.DependOn(() => new IndividualMovement()); [Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn(); [Dependency] public IFoodFactoryApi FoodFactoryApi => this.DependOn(); // private readonly AutoList _items = []; // public IAutoList Items => _items; // public readonly Sorted1DList Items = new(pos => pos.BeltT); private readonly Ordered1DList _items = new(); public Ordered1DList Items => _items; // private AutoList.Binding _itemsBinding; public IBeltPort StartPort { get; set; } // public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() { // Face = Direction.Back, // MovementConveyor = this, // LocalOffset = Position, // BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start), // PullPush = TransferMode.PushPull , // Direction = PortAccess.BiDirectional, // AcceptItemFunc = (item,offset)=>{ _items.Insert(0,new ConveyorSlice(item,offset)); return true;}, // CanAcceptItemFunc = (item, offset) => // { // return .25f <= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, 5f, LaneSpan.One); // } // }; public IBeltPort EndPort { get; set; } // public IBeltSlotProfile EndPort => new ItemConveyor.ConveyorPort() { // Face = Direction.Front, // MovementConveyor = this, // Direction = PortAccess.BiDirectional, // LocalOffset = Position, // BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End), // PullPush = TransferMode.PushPull, // AcceptItemFunc = (item,offset)=>{ _items.Add(new ConveyorSlice(item,Length-offset)); return true;}, // CanAcceptItemFunc = (item,offset)=> .25f >= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart,Length,.5f,LaneSpan.One) // }; public IList OtherPorts = []; // [Export] public Vector3I Position { get; set; } = default!; //Speed per unit time private readonly AutoValue _speed = new(.5f); public IAutoValue SpeedValue => _speed; public float SignedSpeed { get => _speed.Value; set => _speed.Value = value; } public float SpeedMagnitude { get => Mathf.Abs(_speed.Value); set => _speed.Value = Mathf.Abs(value) * Mathf.Sign(_speed.Value); } public bool IsReversed { get => _speed.Value < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); } // public IList Items => _items; public float Length { get; set; } = 1; // public IEnumerable EnumerateTowardEnd() => _items; // public IEnumerable EnumerateTowardStart() => _items.Reverse(); public BeltDirection GetBeltDirection() => IsReversed ? BeltDirection.TowardStart : BeltDirection.TowardEnd; public IBeltMovement GetMovementPolicy() => MovementSystem; public IEnumerable GetPorts() => [StartPort, EndPort, .. OtherPorts]; public void OnResolved() { if (Engine.IsEditorHint()) { return; } // var timer = new Timer() { WaitTime = .05f, Autostart = true }; // AddChild(timer); FoodFactoryApi.TickManger.GameTick += args => MovementSystem.AdvanceBelt(this, (float)args.Delta); if (StartPort is null || EndPort is null) { throw new Exception(); } foreach (var item in GetPorts()) { var points = item.Points(); var guid = GridRegistry.Register(item, [.. points]); TreeExiting += () => GridRegistry.UnRegister(guid); } } //ChatGPT Assisted public BeltObstacle GetDistanceToNextItem( BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet? visited = null) { if (beltDirection == BeltDirection.NotMoving) { throw new NotSupportedException(); } visited ??= []; if (!visited.Add(this)) { return new BeltObstacle(0); } bool towardStart = beltDirection == BeltDirection.TowardStart; // 1️⃣ Try to find next item on this conveyor var nextItem = FindNextLocalItem(towardStart, itemBeltT, laneSpan); // GD.Print(nextItem); if (nextItem.HasValue(out var item)) { return new ItemBeltObstacle(MathF.Abs(itemBeltT - item.BeltT) - ITEMSIZE, item, laneSpan); // return MathF.Abs(itemBeltT - item.BeltT);//TODO UseLaneSpan inadditon } // 2️⃣ No local item → try crossing into adjacent conveyor var recursiveDistance = GetDistanceAcrossPort( towardStart, itemBeltT, maxDistToCheck, laneSpan, visited ); return recursiveDistance;// with { Distance = recursiveDistance.Distance + Length }; } //ChatGPT Assisted private IOption FindNextLocalItem( bool towardStart, float itemBeltT, LaneSpan laneSpan) { var sequence = towardStart ? _items.EnumerateTowardStart(itemBeltT) : _items.EnumerateTowardEnd(itemBeltT); while (sequence.MoveNext()) { var item = sequence.Current; if (towardStart ? item.Position < itemBeltT : item.Position > itemBeltT) { return Some.Of(new ConveyorSlice(item.Value, item.Position)); } } return None.Of(); // return sequence // .Where(i => towardStart ? i.Value.BeltT < itemBeltT : i.Value.BeltT > itemBeltT) // .Where(i => LaneSpan.OverLaps(i.Value.LaneSpan, laneSpan)) // .Select(item => item.Value) // .FirstOrNone(); } //ChatGPT Assisted private BeltObstacle GetDistanceAcrossPort( bool towardStart, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet visited) { var port = towardStart ? StartPort : EndPort; var boundaryT = towardStart ? 0f : Length; var distanceToBoundary = MathF.Abs(itemBeltT - boundaryT); // 🚫 Boundary already exceeds budget // if (distanceToBoundary >= maxDistToCheck) // { // return maxDistToCheck; // } var neighborPort = FindFacingConveyorPort(port); if (!neighborPort.HasValue(out var conveyorPort)) { var o = GetPortFacing(port); if (o.HasValue(out var otherport)) { var mappedLane2 = MapLaneOrFail(port, otherport, laneSpan); if (mappedLane2.HasValue(out var lane2)) { return new PortBeltObstacle(distanceToBoundary, lane2, otherport); // return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary+1,lane1,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT } return new BoundaryBeltObstacle(distanceToBoundary); // return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary,laneSpan,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT } return new BoundaryBeltObstacle(distanceToBoundary); } if (conveyorPort.BeltT is not BeltTEnd end) { return new BoundaryBeltObstacle(distanceToBoundary); } var mappedLane = MapLaneOrFail(port, conveyorPort, laneSpan); if (!mappedLane.HasValue(out var lane)) { return new BoundaryBeltObstacle(distanceToBoundary); } var nextDirection = DirectionAwayFromEnd(end.End); var startT = end.End == ConveyorEnd.Start ? 0f : conveyorPort.Conveyor.Length; // 🔻 Remaining budget after reaching boundary // var remainingDist = maxDistToCheck - distanceToBoundary; // if (remainingDist <= 0f) // { // return maxDistToCheck; // } var recursiveDistance = conveyorPort.Conveyor.GetDistanceToNextItem( nextDirection, startT, maxDistToCheck, lane, visited ); return recursiveDistance with { Distance = recursiveDistance.Distance + distanceToBoundary }; } private IOption FindFacingConveyorPort( IBeltPort slot) { var targetpos = slot.Profile.LocalOffset.TransformDirection(Vector3I.Forward); // var targetPos = port.Profile.Position + port.Profile.Face.ToVector(); return GridRegistry .Get(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward)) .Where(port => port is ConveyorPort) .Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back)) .FirstOrNone() .Bind(p => p is ConveyorPort cp ? cp.ToOption() : None.Of()); } public IOption GetPortFacing(IBeltPort slot) => slot.GetPortFacing(GridRegistry); private IOption MapLaneOrFail( IBeltPort from, IBeltPort to, LaneSpan incoming) { // return None.Of(); bool mirror = false; // full mapping of the source port var mapped = from.MapLaneSpanToFacingPort(to); if (mapped == LaneSpan.Zero) return LaneSpan.Zero.ToOption(); int sourceWidth = from.Profile.Width; int targetWidth = mapped.Width; int startOffset = incoming.Start; // start relative to source port int spanLength = incoming.Width; int newStart; if (mirror) { // mirrored: right side of incoming aligns with right side of mapped newStart = mapped.End - (startOffset + spanLength); } else { // normal: left side of incoming aligns with left side of mapped newStart = mapped.Start + startOffset; } int newEnd = newStart + spanLength; // clamp to mapped range newStart = Math.Max(mapped.Start, newStart); newEnd = Math.Min(mapped.End, newEnd); // GD.Print(incoming, new LaneSpan((ushort)newStart, (ushort)newEnd)); if (newStart >= newEnd) { // GD.Print("none"); return None.Of(); } return new LaneSpan((ushort)newStart, (ushort)newEnd).ToOption(); } private BeltDirection DirectionAwayFromEnd(ConveyorEnd conveyorEnd) => conveyorEnd switch { ConveyorEnd.Start => BeltDirection.TowardEnd, ConveyorEnd.End => BeltDirection.TowardStart, _ => throw new NotSupportedException() }; // public IEnumerable.ItemHandle> EnumerateTowardEnd() => Items.EnumerateTowardEnd(); // public IEnumerable.ItemHandle> EnumerateTowardStart() => Items.EnumerateTowardStart(); private static readonly Vector3[] _square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)]; public const float ITEMSIZE = .2f; private const float ITEMHALFSIZE = ITEMSIZE / 2f; // public override void _Process(double delta) // { // if (StartPort is null || EndPort is null) // { // return; // } // foreach (var item in GetPorts()) // { // for (int ii = 0; ii < item.Profile.Width; ii++) // { // DebugDraw3D.DrawLinePath(_square.Select(i => item.Profile.LocalOffset.ToGodot().TranslatedLocal(i).TranslatedLocal(new Vector3(ii, 0, 0)).Origin).ToArray(), (!Engine.IsEditorHint()) && GetPortFacing(item).HasValue() ? Colors.Green : Colors.Red); // } // // DebugDraw3D.DrawArrow(item.Profile.LocalOffset.Origin, item.Profile.LocalOffset.LocalToWorld(item.Profile.Face.ToVector()),(!Engine.IsEditorHint())&&GetPortFacing(item).HasValue()?Colors.Green:Colors.Red); // } // } public ConveyorPort CreatePort(BeltPortProfile profile, BeltT beltT, LaneSpan laneSpan) { // var towardStartEnd = beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End; bool accept(IBeltItem item, float offset, LaneSpan lane) { if (beltT is BeltTEnd end) { var startBeltT = end.End == ConveyorEnd.Start ? 0 : Length; var obstacle = GetDistanceToNextItem(end.End.Swap().DirectionTo(), startBeltT, offset, lane); var distanceAllowed = obstacle.Distance; return ITEMSIZE < distanceAllowed; } else if (beltT is BeltTOffset endOffset) { return HasClearance(endOffset.T, lane); } throw new NotImplementedException(); } bool tryInsert(IBeltItem item, float offset, LaneSpan laneSpan) { if (beltT is BeltTEnd end) { if (end.End == ConveyorEnd.End) { _items.Insert(item, Length - offset); // Items.Insert(new(item, Length - offset) { LaneSpan = laneSpan }); // _items.Add(new(item, Length-offset)); } else { _items.Insert(item, offset); // Items.Insert(new(item, offset) { LaneSpan = laneSpan }); // _items.Insert(0, new(item, offset)); } return true; } else if (beltT is BeltTOffset endOffset) { if (!HasClearance(endOffset.T, LaneSpan.One)) { return false; } _items.Insert(item, endOffset.T); // Items.Insert(new(item, endOffset.T) { LaneSpan = laneSpan }); // int insertIndex = FindInsertIndex(endOffset.T); // _items.Insert(insertIndex, new(item, endOffset.T)); return true; } // _items.Insert(0,new ConveyorSlice(item,0)); // return true; throw new NotImplementedException(); } var port = new ConveyorPort(this, profile, beltT, accept, tryInsert); return port; } private bool HasClearance(float centerT, LaneSpan span) { var lower = GetDistanceToNextItem( BeltDirection.TowardStart, centerT, ITEMSIZE, span ); var upper = GetDistanceToNextItem( BeltDirection.TowardEnd, centerT, ITEMSIZE, span ); float lowerAllowed = lower.Distance;// - // (lower.IsItem ? ItemConveyor.ITEMSIZE : 0); float upperAllowed = upper.Distance;// - // (upper.IsItem ? ItemConveyor.ITEMSIZE : 0); return ITEMSIZE < lowerAllowed && ITEMSIZE < upperAllowed; } public Ordered1DList.Enumerator EnumerateTowardEnd() => _items.EnumerateTowardEnd(-1); public Ordered1DList.Enumerator EnumerateTowardStart() => _items.EnumerateTowardStart(_items.Count); public bool HasItems() => _items.Count > 0; } //TODO Should liklely account for max search distance where the conveyorm may be needed to know public record class BeltObstacle(float Distance); public record ItemBeltObstacle(float Distance, ConveyorSlice Item, LaneSpan LaneSpan) : BeltObstacle(Distance); public record BoundaryBeltObstacle(float DistanceToBoundary) : BeltObstacle(DistanceToBoundary); public record PortBeltObstacle(float DistanceToBoundary, LaneSpan LaneSpan, IBeltPort BeltPort) : BoundaryBeltObstacle(DistanceToBoundary);