This commit is contained in:
2026-04-15 22:22:56 -04:00
commit 5906f248f4
90 changed files with 6345 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
using ChickenGameTest;
using Godot;
using System;
public partial class ConveyorBeltStraight : Node3D
{
[Export] public Vector3I VoxelPos { get; set; } = default;
[Export] public int Width { get; set; } = default;
public GridTransform3D VoxelTransform
{
get => GridTransform3D.FromGodot(Transform);
set => Transform = value.ToGodot();
}
public override void _Ready()
{
var node = GetNode("Node") as TestItemConveyor;
base._Ready();
// GD.PrintS(VoxelTransform.Origin,VoxelTransform.Forward);
node.StartPort = node.CreatePort(
new(VoxelTransform.TranslatedLocal(new(1-Width,0,0)).RotateY(2),
// Direction.Back,
Width,
PortAccess.BiDirectional),
new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
new LaneSpan(0, 1));
// GD.Print(VoxelTransform.Origin,GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)).Origin);
node.EndPort = node.CreatePort(
new(VoxelTransform, //new(GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)),
// Direction.Front,
Width,
PortAccess.BiDirectional),
new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
new LaneSpan(0, 1));
// node.OtherPorts = [
// node.CreatePort(
// new(VoxelTransform.RotateY90CW(),
// // Direction.Right,
// 1,
// PortAccess.In),
// new ItemConveyor.BeltTOffset(node.Length/2),
// LaneSpan.One),
// node.CreatePort(
// new(VoxelTransform.RotateY90CCW(),
// // Direction.Left,
// 1,
// PortAccess.In),
// new ItemConveyor.BeltTOffset(node.Length/2),
// LaneSpan.One)
// ];
// new ConveyorPort(this,
// new BeltPortProfile(VoxelPos, Direction.Back, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
// (item, offset) =>
// {
// var obstacle = node.GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// },
// (item, offset) => { _items.Insert(0, new(item, offset)); return true;});
// node.EndPort = new ConveyorPort(this,
// new BeltPortProfile(VoxelPos, Direction.Front, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
// (item, offset) =>
// {
// var obstacle = node.GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, node.Length, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One);
// },
// (item, offset) => { _items.Add(new(item, offset)); return true;});
}
}

View File

@@ -0,0 +1 @@
uid://dgyuh6un1qoj4

View File

@@ -0,0 +1,34 @@
[gd_scene load_steps=7 format=3 uid="uid://c4h7mwnfrdesg"]
[ext_resource type="Script" uid="uid://dgyuh6un1qoj4" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs" id="1_mn8ro"]
[ext_resource type="Script" uid="uid://j24fuotdwwx4" path="res://src/VoxelGrid/TestItemConveyor.cs" id="2_1vr1d"]
[ext_resource type="PackedScene" uid="uid://bapdy1c6rb4qr" path="res://assets/kenney_conveyor-kit/Models/GLB format/conveyor-sides.glb" id="2_mn8ro"]
[ext_resource type="PackedScene" uid="uid://ccuhhy2oa8xvm" path="res://assets/kenney_conveyor-kit/Models/GLB format/arrow-basic.glb" id="3_1vr1d"]
[ext_resource type="Script" uid="uid://bjuntmf2sjynp" path="res://src/VoxelGrid/ConveyorItemRender.cs" id="4_g7n1f"]
[sub_resource type="Curve3D" id="Curve3D_x5qdr"]
_data = {
"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0, 0, 0, 0, 0, 0, 0, 0.5, -0.5),
"tilts": PackedFloat32Array(0, 0)
}
point_count = 2
[node name="ConveyorBeltStraight" type="Node3D"]
script = ExtResource("1_mn8ro")
[node name="Node" type="Node" parent="."]
script = ExtResource("2_1vr1d")
[node name="conveyor-sides" parent="." instance=ExtResource("2_mn8ro")]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0)
[node name="arrow-basic2" parent="." instance=ExtResource("3_1vr1d")]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0.21879572, 0)
[node name="Path3D" type="Path3D" parent="."]
curve = SubResource("Curve3D_x5qdr")
[node name="Node2" type="Node" parent="." node_paths=PackedStringArray("Path3D", "ItemConveyor")]
script = ExtResource("4_g7n1f")
Path3D = NodePath("../Path3D")
ItemConveyor = NodePath("../Node")

View File

@@ -0,0 +1,133 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
// public partial class StructuralInstance<TAttribute> where TAttribute : class
// {
public sealed class StructuralBuilder<TAttribute> where TAttribute : class
{
private readonly Dictionary<int, TAttribute> _components = [];
public StructuralBuilder() { }
public StructuralBuilder(IStructuralInstance<TAttribute> existing)
{
foreach (var (id, value) in existing.GetAttributes())
{
_components[id] = value;
}
// for (int i = 0; i < existing.ComponentCount; i++)
// {
// var typeId = existing._attributesTypeIds[i];
// var component = existing.GetComponentAt(i);
// _components[typeId] = component;
// }
}
public StructuralBuilder<TAttribute> Add<T>(T component) where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
_components[id] = component;
return this;
}
public StructuralBuilder<TAttribute> Upsert<T>(Func<T, T> ifExists, Func<T> none) where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
_components[id] = _components.TryGetValue(id, out var old) ? ifExists((T)old) : none();
return this;
}
public StructuralBuilder<TAttribute> CombineWith(IStructuralInstance<TAttribute> other, Action<CombineBinder>? configure = null)
{
var binder = new CombineBinder();
configure?.Invoke(binder);
foreach (var (id, value) in other.GetAttributes())
{
if (binder._ignores.Contains(id))
{
continue;
}
if (_components.TryGetValue(id, out var existing)
&& binder._rules.TryGetValue(id, out var rule))
{
_components[id] = rule.Invoke(existing, value);
continue;
}
_components[id] = value;
}
return this;
}
public StructuralBuilder<TAttribute> Remove<T>() where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
_components.Remove(id);
return this;
}
public bool Has<T>() where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
return _components.ContainsKey(id);
}
public StructuralInstance<TAttribute> Build()
{
int count = _components.Count;
var typeIds = new int[count];
var components = new TAttribute[count];
int i = 0;
foreach (var kv in _components)
{
typeIds[i] = kv.Key;
components[i] = kv.Value;
i++;
}
Array.Sort(typeIds, components);
return new StructuralInstance<TAttribute>(typeIds, components);
}
public MutableStructuralInstance<TAttribute> BuildMutable()
{
// int count = _components.Count;
var sortedList = new SortedList<int, TAttribute>(_components);
// var typeIds = new int[count];
// var components = new TAttribute[count];
// int i = 0;
// foreach (var kv in _components)
// {
// typeIds[i] = kv.Key;
// components[i] = kv.Value;
// i++;
// }
// Array.Sort(typeIds, components);
return new MutableStructuralInstance<TAttribute>(sortedList);
}
public class CombineBinder
{
internal readonly Dictionary<int, Func<TAttribute, TAttribute, TAttribute>> _rules = [];
internal readonly HashSet<int> _ignores = [];
public CombineBinder Bind<T>(Func<T, T, T> func) where T : TAttribute
{
_rules[ComponentType<T>.Id] = (a, b) => func((T)a, (T)b);
return this;
}
public CombineBinder Ignore<T>() where T : TAttribute
{
_ignores.Add(ComponentType<T>.Id);
return this;
}
}
}
// }

View File

@@ -0,0 +1 @@
uid://bk721rnhegl0x

View File

@@ -0,0 +1,255 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Linq;
using Chickensoft.Sync.Primitives;
using SJK.Functional;
public interface IStructuralInstance<TAttribute> : IEquatable<IStructuralInstance<TAttribute>>
{
int ComponentCount { get; }
bool Has<T>() where T : TAttribute;
Option<T> Get<T>() where T : TAttribute;
// TAttribute GetComponentAt(int index);
IEnumerable<(int Id, TAttribute Value)> GetAttributes();
bool IEquatable<IStructuralInstance<TAttribute>>.Equals(IStructuralInstance<TAttribute>? other) => Enumerable.SequenceEqual(GetAttributes(), other.GetAttributes(), EqualityComparer<(int, TAttribute)>.Default);
int ComputeHashCode()
{
HashCode hash = new HashCode();
foreach (var item in GetAttributes())
{
hash.Add(item);
}
return hash.ToHashCode();
}
}
public sealed partial class StructuralInstance<TAttribute> : IStructuralInstance<TAttribute>, IEquatable<StructuralInstance<TAttribute>> where TAttribute : class
{
private readonly int _hashCode;
internal readonly int[] _attributesTypeIds;
internal readonly TAttribute[] _attributes;
public int ComponentCount => _attributes.Length;
public TAttribute GetComponentAt(int index) => _attributes[index];
internal StructuralInstance(int[] componentTypeIds, TAttribute[] attributes)
{
_attributesTypeIds = componentTypeIds;
_attributes = attributes;
_hashCode = (this as IStructuralInstance<TAttribute>).ComputeHashCode();
}
public bool Has<T>() where T : TAttribute
{
// int typeId = ComponentTypeRegistry.GetId<T>();
var typeId = ComponentType<T>.Id;
return Array.BinarySearch(_attributesTypeIds, typeId) >= 0;
}
public Option<T> Get<T>() where T : TAttribute
{
// int typeId = ComponentTypeRegistry.GetId<T>();
var typeId = ComponentType<T>.Id;
int index = Array.BinarySearch(_attributesTypeIds, typeId);
if (index < 0)
{
return Option<T>.None;
}
return Option<T>.Some((T)_attributes[index]);
}
public bool Equals(StructuralInstance<TAttribute>? other)
{
if (ReferenceEquals(this, other))
{
return true;
}
if (other is null)
{
return false;
}
return _hashCode == other._hashCode && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer<TAttribute>.Default);// && AttributesAreEqual(other);
}
public override bool Equals(object? obj) => obj is StructuralInstance<TAttribute> value? Equals(value) : obj is IStructuralInstance<TAttribute> v && v.Equals(this);
public override int GetHashCode() => _hashCode;
public IEnumerable<(int Id, TAttribute Value)> GetAttributes()
{
for (int i = 0; i < ComponentCount; i++)
{
yield return (_attributesTypeIds[i], _attributes[i]);
}
}
public override string ToString() => base.ToString();
}
public sealed class MutableStructuralInstance<TAttribute> : IStructuralInstance<TAttribute>, IEquatable<MutableStructuralInstance<TAttribute>> where TAttribute : class
{
private readonly SortedList<int, TAttribute> _attributes = [];
public int ComponentCount => _attributes.Count;
public MutableStructuralInstance(SortedList<int, TAttribute> attributes)
{
_attributes = attributes;
}
public Option<T> Get<T>() where T : TAttribute => _attributes.TryGetValue(ComponentType<T>.Id, out var attribute) ? Option<T>.Some((T)attribute) : Option<T>.None;
public IEnumerable<(int Id, TAttribute Value)> GetAttributes() => _attributes.Select(x => (x.Key, x.Value));
public TAttribute GetComponentAt(int index) => _attributes[index];
public bool Has<T>() where T : TAttribute => _attributes.ContainsKey(ComponentType<T>.Id);
public void Set<T>(T value) where T : TAttribute => _attributes[ComponentType<T>.Id] = value;
public override int GetHashCode() => (this as IStructuralInstance<TAttribute>).ComputeHashCode();
public bool Equals(MutableStructuralInstance<TAttribute>? other) => other is not null && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer<KeyValuePair<int, TAttribute>>.Default);
public override bool Equals(object? obj) => obj is MutableStructuralInstance<TAttribute> other ? Equals(other) : obj is IStructuralInstance<TAttribute> v && v.Equals(this);
}
public static class ComponentTypeRegistry
{
private static readonly Dictionary<Type, int> _typeToId = [];
private static readonly List<Type> _idToType = [];
public static int GetId(Type type)
{
if (_typeToId.TryGetValue(type, out var id))
{
return id;
}
id = _idToType.Count;
_typeToId[type] = id;
_idToType.Add(type);
return id;
}
public static int GetId<T>() => GetId(typeof(T));
}
public static class ComponentType<T>
{
public static readonly int Id = ComponentTypeRegistry.GetId<T>();
public static readonly bool CacheTransitions = Resolve();
private static bool Resolve()
{
var attr = typeof(T).GetCustomAttributes(typeof(ComponentOptionsAttribute), false);
return attr.OfType<ComponentOptionsAttribute>().FirstOrNone().Map(static f => f.CacheTransitions).Or(true);
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class ComponentOptionsAttribute : Attribute
{
public bool CacheTransitions { get; init; } = true;
}
public interface IStructureRegistry<TAttribute> where TAttribute : class
{
StructuralInstance<TAttribute> Canonicalize(StructuralInstance<TAttribute> value);
StructuralInstance<TAttribute> AddOrReplaceAttribute<T>(StructuralInstance<TAttribute> instance, T attribute) where T : TAttribute;
StructuralInstance<TAttribute> RemoveAttribute<T>(StructuralInstance<TAttribute> instance) where T : TAttribute;
}
public class StructuralInstanceManger<TAttribute> : IStructureRegistry<TAttribute> where TAttribute : class
{
private readonly HashSet<StructuralInstance<TAttribute>> _instances = [];
public StructuralInstance<TAttribute> Add<T>(StructuralInstance<TAttribute> instance, T component) where T : TAttribute
{
var builder = new StructuralBuilder<TAttribute>(instance).Add(component);
return Canonicalize(builder.Build());
}
public StructuralInstance<TAttribute> Canonicalize(StructuralInstance<TAttribute> value)
{
if (_instances.TryGetValue(value, out var id))
{
return id;
}
_instances.Add(value);
return value;
}
public StructuralInstance<TAttribute> AddOrReplaceAttribute<T>(StructuralInstance<TAttribute> instance, T attribute) where T : TAttribute
{
if (!graph.TryGetValue(instance, out var results))
{
graph[instance] = results = [];
}
for (int i = 0; i < results.Count; i++)
{
if (results[i] is AddTransitionEntry<T> addTransitionEntry && EqualityComparer<T>.Default.Equals(addTransitionEntry.Value, attribute))
{
return addTransitionEntry.Result;
}
}
var result =new AddTransitionEntry<T>(ComponentType<T>.Id, attribute, Canonicalize(new StructuralBuilder<TAttribute>(instance).Add(attribute).Build()));
if (ComponentType<T>.CacheTransitions)
{
results.Add(result);
}
return result.Result;
}
public StructuralInstance<TAttribute> RemoveAttribute<T>(StructuralInstance<TAttribute> instance) where T : TAttribute
{
if (!graph.TryGetValue(instance, out var results))
{
graph[instance] = results = [];
}
var id = ComponentType<T>.Id;
for (int i = 0; i < results.Count; i++)
{
if (results[i] is RemoveTransitionEntry removeTransitionEntry && removeTransitionEntry.Id == id)
{
return removeTransitionEntry.Result;
}
}
var result =new RemoveTransitionEntry(id, Canonicalize(new StructuralBuilder<TAttribute>(instance).Remove<T>().Build()));
if (ComponentType<T>.CacheTransitions)
{
results.Add(result);
}
return result.Result;
}
private Dictionary<StructuralInstance<TAttribute>, List<TransitionEntry>> graph = [];
private record TransitionEntry(int Id);
private record AddTransitionEntry<T>(int Id, T Value, StructuralInstance<TAttribute> Result) : TransitionEntry(Id) where T : TAttribute;
private record RemoveTransitionEntry(int Id, StructuralInstance<TAttribute> Result) : TransitionEntry(Id);
// private record ModifyTransitionEntry<T>(int Id, TAttribute Value, StructuralInstance<TAttribute> Result) : TransitionEntry(Id) where T : TAttribute;
}
public class AutoStructureInstance<TAttribute> where TAttribute : class
{
private StructuralInstance<TAttribute> _structuralInstance;
private IStructureRegistry<TAttribute> _registry;
private Dictionary<int,List<Delegate>> _bindings = [];
public void Set<T>(T? value) where T : TAttribute
{
var old = _structuralInstance.Get<T>();
if (old.HasValue && old.Value.Equals(value))
{
return;
}
if (value is null)
{
_structuralInstance = _registry.RemoveAttribute<T>(_structuralInstance);
}
else
{
_structuralInstance = _registry.AddOrReplaceAttribute(_structuralInstance, value);
}
_bindings[ComponentType<T>.Id].ForEach(item => item.DynamicInvoke(old,value));
}
public void Bind<T>(Action<T?, T?> callback) where T : TAttribute
{
int id = ComponentType<T>.Id;
if (!_bindings.TryGetValue(id, out var list))
{
_bindings[id] = list = [];
}
list.Add(callback);
}
}

View File

@@ -0,0 +1 @@
uid://cexmjk01dgtbe

View File

@@ -0,0 +1,69 @@
namespace ChickenGameTest;
using System.Diagnostics;
using Godot;
using SJK.Functional;
public class TestStructural
{
public static void Test()
{
var a = new StructuralBuilder<object>()
.Add(new Vector3(0,0,0))
.Add(new Vector2(0,5))
.Add(new Vector4(0,0,0,0))
.Add(new Aabb())
.Add(new int())
.Add(new float())
.Add(new Node())
.Add(new Node())
.Add(new Node2D())
.Add("")
.Add(new test(){a = 5, b = "gg"})
.Build();
var registy = new StructuralInstanceManger<object>();
var b = registy.Add(a,new Vector2(1,2));
var c = registy.Add(a,new Vector2(1,2));
var d = registy.Canonicalize(new StructuralBuilder<object>(a).Add(new Vector2(1,2)).Build());
var g = new StructuralBuilder<object>(a).Add(Option<int>.Some(5)).Build();
var h = new StructuralBuilder<object>(a).Add(Option<int>.Some(5)).BuildMutable();
GD.Print(g.Equals(h));
GD.Print(ReferenceEquals(b,c));
GD.Print(ReferenceEquals(b,d));
GD.Print(ComponentType<Vector3>.Id);
GD.Print(ComponentType<Vector2>.Id);
var help = new StructuralBuilder<object>(a)
.CombineWith(b, static configure => configure
.Bind<Vector2>(static (a, b) => a + b)
.Ignore<Vector4>())
.Add(Vector2I.Zero)
.Build();
GD.Print(help.Get<Vector2>().Map(i=>$"{i}").OrDefault("none"));
var timer = new Stopwatch();
timer.Start();
for (int i = 0; i < 100000; i++)
{
var e = registy.AddOrReplaceAttribute(a,new test());
}
timer.Stop();
GD.Print(timer.Elapsed);
timer.Reset();
timer.Start();
for (int i = 0; i < 100000; i++)
{
var e = registy.Canonicalize(new StructuralBuilder<object>(a).Add(new test()).Build());
}
timer.Stop();
GD.Print(timer.Elapsed);
}
}
[ComponentOptions(CacheTransitions = true)]
record test
{
public int a;
public string b;
}

View File

@@ -0,0 +1 @@
uid://br6rfqtryq0cx

15
src/Game.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace ChickenGameTest;
using Godot;
public partial class Game : Control
{
public Button TestButton { get; private set; } = default!;
public int ButtonPresses { get; private set; }
public override void _Ready()
=> TestButton = GetNode<Button>("%TestButton");
public void OnTestButtonPressed() => ButtonPresses++;
}

1
src/Game.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://bcadf3uhcfy2

30
src/Game.tscn Normal file
View File

@@ -0,0 +1,30 @@
[gd_scene load_steps=2 format=3 uid="uid://cywpu6lxdjhuu"]
[ext_resource type="Script" path="res://src/Game.cs" id="1_17mmo"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_17mmo")
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
layout_mode = 2
[node name="TestButton" type="Button" parent="CenterContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Test Button"
[connection signal="pressed" from="CenterContainer/VBoxContainer/TestButton" to="." method="OnTestButtonPressed"]

268
src/Items/Item.cs Normal file
View File

@@ -0,0 +1,268 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Godot;
/// <summary>
/// Items that can be on a belt
/// </summary>
public interface IBeltItem : IDisposable
{
int Width { get; }
int Height { get; }
Node3D CreateItemVisual();
delegate void ItemRemoved(IBeltItem item);
event ItemRemoved Disposed;
}
public class TestItem() : IBeltItem
{
public float Temp {get;set;}
public int Width {get;set;}
public int Height {get;set;}
public Node3D CreateItemVisual() => new MeshInstance3D(){Mesh = new BoxMesh(){Size = new(.1f,.1f,.1f)}};
bool _disposed;
public event IBeltItem.ItemRemoved Disposed;
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
Disposed?.Invoke(this);
}
}
public interface IFluidItem
{
float Volume { get; }
}
public interface IConveyorProfile
{
int LaneCount { get; }
bool LaneSwitching { get; }
}
public readonly record struct BeltPortProfile(
GridTransform3D LocalOffset,
// Direction Face,
int Width,
PortAccess Access
) : IBeltSlotProfile
{
public Vector3I Position => LocalOffset.Origin;
// Direction IBeltSlotProfile.Direction => Face;
}
public interface IBeltPort
{
BeltPortProfile Profile { get; }
bool CanAccept(
IBeltItem item,
LaneSpan laneSpan,
float beltT
);
bool TryInsert(
IBeltItem item,
LaneSpan laneSpan,
float beltT
);
IEnumerable<Vector3I> Points()
{
for (int i = 0; i < Profile.Width; i++)
{
yield return (Profile.LocalOffset.Right * i) + Profile.LocalOffset.Origin;
}
}
LaneSpan LaneSpan => new LaneSpan(0,(ushort)Profile.Width);
}
public sealed class ConveyorPort : IBeltPort
{
public BeltPortProfile Profile { get; }
public IMovementConveyor Conveyor { get; }
public ItemConveyor.BeltT BeltT { get; }
private readonly Func<IBeltItem, float, LaneSpan, bool>? _canAccept;
private readonly Func<IBeltItem, float, LaneSpan, bool>? _accept;
public ConveyorPort(
IMovementConveyor conveyor,
BeltPortProfile profile,
ItemConveyor.BeltT beltT,
Func<IBeltItem, float, LaneSpan, bool>? canAccept,
Func<IBeltItem, float, LaneSpan, bool>? accept)
{
Conveyor = conveyor;
Profile = profile;
BeltT = beltT;
_canAccept = canAccept;
_accept = accept;
}
public bool CanAccept(
IBeltItem item,
LaneSpan laneSpan,
float beltT)
{
if (!Profile.Access.HasFlag(PortAccess.In))
{
return false;
}
if (_canAccept is null || _accept is null)
{
return false;
}
// LaneSpan checks live here, not in delegates
if (!LaneSpan.Encapsulates(
new LaneSpan(0, (ushort)Profile.Width),
laneSpan))
{
return false;
}
return _canAccept(item, beltT, laneSpan);
}
public bool TryInsert(
IBeltItem item,
LaneSpan laneSpan,
float beltT)
{
if (!CanAccept(item, laneSpan, beltT))
{
return false;
}
return _accept!(item, beltT, laneSpan);
}
}
public interface IBeltSlotProfile
{
Vector3I Position { get; }
// Direction Direction { get; }
int Width { get; }
PortAccess Access { get; }
// bool CanAcceptItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0);
// /// <summary>
// ///Tries to insert an belt item offset by laneSpan,
// /// </summary>
// /// <param name="beltItem"></param>
// /// <param name="laneSpan"></param>
// /// <param name="beltT"></param>
// /// <returns></returns>
// bool TryInsertItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0);
}
public record LaneId(int Index);
[Flags]
public enum PortAccess : byte
{
// Disabled = 0,
In = 1,
Out = 2,
InOut = In | Out,
BiDirectional = InOut
}
[Flags]
public enum TransferMode : byte//Need Better Name
{
Passive = 0,
Push = 1,
Pull = 2,
PushPull = Push | Pull
}
public static class SlotExtesion
{
public static LaneSpan MapLaneSpanToFacingPort(this IBeltPort self, IBeltPort other) => MapSlotToFacingSlot(self.Profile.LocalOffset, self.Profile.Width, other.Profile.LocalOffset, other.Profile.Width);
public static LaneSpan MapSlotToFacingSlot(
GridTransform3D fromTx,
int fromWidth,
GridTransform3D toTx,
int toWidth)
{
ushort minLane = ushort.MaxValue;
ushort maxLane = ushort.MinValue;
// In port-local space:
// +Z = forward
// +X = width axis
// origin = port center / lane 0 start
// (adjust if your convention differs)
for (int i = 0; i < fromWidth; i++)
{
var fromLocalLane = new Vector3I(i, 0, 0);
var fromLocalFacing = fromLocalLane + Vector3I.Forward;
var worldFacing = fromTx.LocalToWorld(fromLocalFacing);
var toLocal = toTx.WorldToLocal(worldFacing);
int laneIndex = toLocal.X;
bool inWidth = laneIndex >= 0 && laneIndex < toWidth;
bool onFacePlane = toLocal.Z == 0; // directly entering target face
if (!inWidth || !onFacePlane)
continue;
minLane = (ushort)Math.Min(minLane, laneIndex);
maxLane = (ushort)Math.Max(maxLane, laneIndex);
}
if (minLane > maxLane)
return LaneSpan.Zero;
return new LaneSpan(minLane, (ushort)(maxLane + 1));
}
// Map a sub-span from 'from' port into the 'to' port space
public static LaneSpan MapLaneSpan(this IBeltPort from, IBeltPort to, LaneSpan incoming)
{
ushort minLane = ushort.MaxValue;
ushort maxLane = ushort.MinValue;
// Loop over each lane in the incoming span
for (int i = incoming.Start; i < incoming.End; i++)
{
// from-local lane
var fromLocalLane = new Vector3I(i, 0, 0);
// shift forward along +Z to "face" the target
var fromLocalFacing = fromLocalLane + Vector3I.Forward;
// world space
var worldFacing = from.Profile.LocalOffset.LocalToWorld(fromLocalFacing);
// target local space
var toLocal = to.Profile.LocalOffset.WorldToLocal(worldFacing);
int laneIndex = toLocal.X;
bool inWidth = laneIndex >= 0 && laneIndex < to.Profile.Width;
bool onFacePlane = toLocal.Z == 0; // entering target face
if (!inWidth || !onFacePlane)
continue;
minLane = (ushort)Math.Min(minLane, laneIndex);
maxLane = (ushort)Math.Max(maxLane, laneIndex);
}
if (minLane > maxLane)
return LaneSpan.Zero;
return new LaneSpan(minLane, (ushort)(maxLane + 1));
}
}

1
src/Items/Item.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://dacksxt66lea4

52
src/Main.cs Normal file
View File

@@ -0,0 +1,52 @@
namespace ChickenGameTest;
using Godot;
using Chickensoft.GameTools.Displays;
#if RUN_TESTS
using System.Reflection;
using Chickensoft.GoDotTest;
#endif
// This entry-point file is responsible for determining if we should run tests.
//
// If you want to edit your game's main entry-point, please see Game.tscn and
// Game.cs instead.
public partial class Main : Node2D
{
public Vector2I DesignResolution => Display.UHD4k;
#if RUN_TESTS
public TestEnvironment Environment = default!;
#endif
public override void _Ready()
{
// Correct any erroneous scaling and guess sensible defaults.
GetWindow().LookGood(WindowScaleBehavior.UIFixed, DesignResolution);
#if RUN_TESTS
// If this is a debug build, use GoDotTest to examine the
// command line arguments and determine if we should run tests.
Environment = TestEnvironment.From(OS.GetCmdlineArgs());
// Environment = new TestEnvironment(true, false, true, false, false, false, null, OS.GetCmdlineArgs());
GD.Print(Environment.ShouldRunTests);
if (Environment.ShouldRunTests)
{
CallDeferred("RunTests");
return;
}
#endif
// If we don't need to run tests, we can just switch to the game scene.
CallDeferred("RunScene");
}
#if RUN_TESTS
private void RunTests()
=> _ = GoTest.RunTests(Assembly.GetExecutingAssembly(), this, Environment);
#endif
private void RunScene()
=> GetTree().ChangeSceneToFile("res://src/Game.tscn");
}

1
src/Main.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://d0cxxx3axr1j

6
src/Main.tscn Normal file
View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://du7hlwjl6vw6l"]
[ext_resource type="Script" path="res://src/Main.cs" id="1_prpoe"]
[node name="Node2D" type="Node2D"]
script = ExtResource("1_prpoe")

222
src/Math/GridTransform3D.cs Normal file
View File

@@ -0,0 +1,222 @@
using Godot;
using System;
public readonly struct GridTransform3D : IEquatable<GridTransform3D>
{
public readonly Vector3I Origin;
// Basis vectors (columns of a rotation matrix)
public readonly Vector3I Right; // local +X
public readonly Vector3I Up; // local +Y
public readonly Vector3I Forward; // local +Z
#region Constructors
public GridTransform3D(
Vector3I origin,
Vector3I right,
Vector3I up,
Vector3I forward)
{
Origin = origin;
Right = right;
Up = up;
Forward = forward;
}
public static GridTransform3D Identity =>
new(
Vector3I.Zero,
Vector3I.Right,
Vector3I.Up,
Vector3I.Forward
);
public static GridTransform3D At(Vector3I origin) =>
new(origin, Vector3I.Right, Vector3I.Up, Vector3I.Forward);
#endregion
#region Transform ops
/// Transform a local point into world space
public Vector3I TransformPoint(Vector3I local)
{
return Origin
+ (Right * local.X)
+ (Up * local.Y)
+ (Forward * local.Z);
}
/// Transform a direction (ignores translation)
public Vector3I TransformDirection(Vector3I dir)
{
return (Right * dir.X)
+ (Up * dir.Y)
+ (Forward * dir.Z);
}
public Vector3I LocalToWorld(Vector3I local)
{
return Origin
+ Right * local.X
+ Up * local.Y
+ Forward * local.Z;
}
public Vector3I WorldToLocal(Vector3I world)
{
Vector3I delta = world - Origin;
return new Vector3I(
Dot(delta, Right),
Dot(delta, Up),
Dot(delta, Forward)
);
}
private static int Dot(Vector3I a, Vector3I b)
=> a.X * b.X + a.Y * b.Y + a.Z * b.Z;
public readonly GridTransform3D Translated(Vector3I offset)
{
return new GridTransform3D( Origin + offset,Right,Up,Forward);
}
public readonly GridTransform3D TranslatedLocal(Vector3I offset)
{
return new GridTransform3D( new Vector3I(Origin.X + Dot(Right,offset), Origin.Y+ Dot(Up,offset), Origin.Z + Dot(Forward,offset)),Right,Up,Forward);
}
#endregion
#region Composition
/// Compose two transforms (this * other)
public GridTransform3D Multiply(GridTransform3D other)
{
return new GridTransform3D(
TransformPoint(other.Origin),
TransformDirection(other.Right),
TransformDirection(other.Up),
TransformDirection(other.Forward)
);
}
public static GridTransform3D operator *(GridTransform3D a, GridTransform3D b)
=> a.Multiply(b);
#endregion
#region Rotations (90° steps)
public GridTransform3D RotateY90CW()
{
// Y stays, X -> Z, Z -> -X
return new GridTransform3D(
Origin,
Forward,
Up,
-Right
);
}
public GridTransform3D RotateY90CCW()
{
// Y stays, X -> -Z, Z -> X
return new GridTransform3D(
Origin,
-Forward,
Up,
Right
);
}
public GridTransform3D RotateX90CW()
{
// X stays, Y -> Z, Z -> -Y
return new GridTransform3D(
Origin,
Right,
Forward,
-Up
);
}
public GridTransform3D RotateZ90CW()
{
// Z stays, X -> Y, Y -> -X
return new GridTransform3D(
Origin,
Up,
-Right,
Forward
);
}
public GridTransform3D RotateY(int quarterTurns)
{
quarterTurns = ((quarterTurns % 4) + 4) % 4;
var t = this;
for (int i = 0; i < quarterTurns; i++)
t = t.RotateY90CW();
return t;
}
#endregion
#region Conversion to/from Godot
public Transform3D ToGodot()
{
var basis = new Basis(
(Vector3)Right,
(Vector3)Up,
(Vector3)Forward
);
return new Transform3D(basis, (Vector3)Origin);
}
public static GridTransform3D FromGodot(Transform3D t)
{
Vector3I RoundAxis(Vector3 v)
{
var a = v.Abs();
if (a.X > a.Y && a.X > a.Z)
return new Vector3I(Math.Sign(v.X), 0, 0);
if (a.Y > a.Z)
return new Vector3I(0, Math.Sign(v.Y), 0);
return new Vector3I(0, 0, Math.Sign(v.Z));
}
return new GridTransform3D(
new Vector3I(
Mathf.RoundToInt(t.Origin.X),
Mathf.RoundToInt(t.Origin.Y),
Mathf.RoundToInt(t.Origin.Z)
),
RoundAxis(t.Basis.X),
RoundAxis(t.Basis.Y),
RoundAxis(t.Basis.Z)
);
}
#endregion
#region Equality
public bool Equals(GridTransform3D other)
{
return Origin == other.Origin
&& Right == other.Right
&& Up == other.Up
&& Forward == other.Forward;
}
public override bool Equals(object? obj)
=> obj is GridTransform3D g && Equals(g);
public override int GetHashCode()
=> HashCode.Combine(Origin, Right, Up, Forward);
#endregion
}

View File

@@ -0,0 +1 @@
uid://dpskwnyblxtf4

View File

@@ -0,0 +1,23 @@
using Godot;
public static partial class SJKMath
{
// public static Vector3I Lerp(Vector3I from, Vector3I to, float weight) => new Vector3I(
// Mathf.Lerp(from.X, to.X, weight),
// Mathf.Lerp(from.Y, to.Y, weight),
// Mathf.Lerp(from.Z, to.Z, weight)
// );
public static Vector3 Lerp(Vector3 from, Vector3 to, float weight) => new Vector3(
Mathf.Lerp(from.X, to.X, weight),
Mathf.Lerp(from.Y, to.Y, weight),
Mathf.Lerp(from.Z, to.Z, weight)
);
// public static Vector2I Lerp(Vector2I from, Vector2I to, float weight) => new Vector2I(
// Mathf.Lerp(from.X, to.X, weight),
// Mathf.Lerp(from.Y, to.Y, weight)
// );
public static Vector2 Lerp(Vector2 from, Vector2 to, float weight) => new Vector2(
Mathf.Lerp(from.X, to.X, weight),
Mathf.Lerp(from.Y, to.Y, weight)
);
}

View File

@@ -0,0 +1 @@
uid://133eop5e4mii

View File

@@ -0,0 +1,42 @@
using System;
using System.Linq;
using Godot;
namespace SJK.GodotHelpers;
public static class MyNodeExtensions{
public static void FreeDeferred(this Node node)=>node.CallDeferred(Node.MethodName.Free);
/// <summary>
/// Checks if given Property exists on <c>Base</c>
/// </summary>
/// <param name="Base">Current <c>GodotObject</c></param>
/// <param name="PropertyName">Property Name</param>
/// <returns><c>bool</c> true if given property exists on Base</returns>
public static bool HasProperty(this GodotObject Base, string PropertyName){
/*
Returns the object's property list as an Godot.Collections.Array of dictionaries.
Each Godot.Collections.Dictionary contains the following entries:
- name is the property's name, as a string;
- class_name is an empty StringName, unless the property is Variant.Type.Object and it inherits from a class;
- type is the property's type, as an int (see Variant.Type);
- hint is how the property is meant to be edited (see PropertyHint);
- hint_string depends on the hint (see PropertyHint);
- usage is a combination of PropertyUsageFlags.
*/
foreach (var Property in Base.GetPropertyListEx())
{
if(Property.Name == PropertyName){
return true;
}
}
return false;
}
public static (Node node, Resource resource, NodePath remaining) GetNodeAndResourceEx(this Node self, NodePath path)
{
var result = self.GetNodeAndResource(path);
return ((Node)result[0], (Resource)result[1], (NodePath)result[2]);
}
public static void QueueFreeChildren(this Node node) => node.GetChildren().ToList().ForEach(item=>item.QueueFree());
public static void QueueFreeChildren(this Node node, Func<Node,bool> predicate) => node.GetChildren().Where(predicate).ToList().ForEach(item=>item.QueueFree());
}

View File

@@ -0,0 +1 @@
uid://bklfdjfp02pav

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Godot;
using Godot.Collections;
using SJK.Functional;
namespace SJK.GodotHelpers.Raycasts;
public record CollisionResultBase(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape
);
public record RaycastResult3D(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape,
Vector3 Position,
Vector3 Normal
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
public record RaycastResult2D(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape,
Vector2 Position,
Vector2 Normal
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
public record ShapeCastResult3D(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape,
Vector3 Point,
Vector3 Normal,
int CollisionCount
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
public record ShapeCastResult2D(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape,
Vector2 Point,
Vector2 Normal,
int CollisionCount
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
public record PointQueryResult3D(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
public record PointQueryResult2D(
GodotObject Collider,
int ColliderId,
Rid Rid,
int Shape
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
#nullable enable
public static class RaycastExtensions
{
// --- 3D ---
public static RaycastResult3D? RaycastEx(this PhysicsDirectSpaceState3D space, Vector3 from, Vector3 to, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
{
var query = new PhysicsRayQueryParameters3D
{
From = from,
To = to,
CollisionMask = collisionMask,
HitFromInside = hitFromInside,
};
if (exclude != null)
query.Exclude = new Array<Rid>(exclude);
var result = space.IntersectRay(query);
if (result.Count == 0)
return null;
return new RaycastResult3D(
Collider: result["collider"].AsGodotObject(),
ColliderId: result["collider_id"].AsInt32(),
Rid: (Rid)result["rid"],
Shape: result["shape"].AsInt32(),
Position: result["position"].AsVector3(),
Normal: result["normal"].AsVector3()
);
}
public static bool RaycastHitEx(this PhysicsDirectSpaceState3D space, Vector3 from, Vector3 to,[NotNullWhen(true)] out RaycastResult3D hit, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
{
hit = space.RaycastEx(from, to, collisionMask, exclude, hitFromInside)!;
return hit is not null;
}
// --- 2D ---
public static RaycastResult2D? RaycastEx(this PhysicsDirectSpaceState2D space, Vector2 from, Vector2 to, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
{
var query = new PhysicsRayQueryParameters2D
{
From = from,
To = to,
CollisionMask = collisionMask,
HitFromInside = hitFromInside,
};
if (exclude != null)
query.Exclude = new Array<Rid>(exclude);
var result = space.IntersectRay(query);
if (result.Count == 0)
return null;
return new RaycastResult2D(
Collider: result["collider"].AsGodotObject(),
ColliderId: result["collider_id"].AsInt32(),
Rid: (Rid)result["rid"],
Shape: result["shape"].AsInt32(),
Position: result["position"].AsVector2(),
Normal: result["normal"].AsVector2()
);
}
public static IOption<RaycastResult2D> RaycastOptionEx(this PhysicsDirectSpaceState2D space, Vector2 from, Vector2 to, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
=> RaycastEx(space, from, to, collisionMask, exclude, hitFromInside).ToOption();
public static bool RaycastHitEx(this PhysicsDirectSpaceState2D space, Vector2 from, Vector2 to, out RaycastResult2D hit, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
{
hit = space.RaycastEx(from, to, collisionMask, exclude, hitFromInside)!;
return hit is not null;
}
// 3D Shape Cast
public static List<CollisionResultBase> IntersectShapeEx(this PhysicsDirectSpaceState3D space, PhysicsShapeQueryParameters3D query, int maxResults = 32)
{
var results = space.IntersectShape(query, maxResults);
var list = new List<CollisionResultBase>(results.Count);
foreach (var dict in results)
{
list.Add(new CollisionResultBase(
Collider: dict["collider"].AsGodotObject(),
ColliderId: dict["collider_id"].AsInt32(),
Rid: (Rid)dict["rid"],
Shape: dict["shape"].AsInt32()
));
}
return list;
}
// 2D Shape Cast
public static List<CollisionResultBase> IntersectShapeEx(this PhysicsDirectSpaceState2D space, PhysicsShapeQueryParameters2D query, int maxResults = 32)
{
var results = space.IntersectShape(query, maxResults);
var list = new List<CollisionResultBase>(results.Count);
foreach (var dict in results)
{
list.Add(new CollisionResultBase(
Collider: dict["collider"].AsGodotObject(),
ColliderId: dict["collider_id"].AsInt32(),
Rid: (Rid)dict["rid"],
Shape: dict["shape"].AsInt32()
));
}
return list;
}
}

View File

@@ -0,0 +1 @@
uid://b8b5eyg6l31o1

View File

@@ -0,0 +1,121 @@
using Godot;
using Godot.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using Array = Godot.Collections.Array;
namespace SJK.GodotHelpers;
/// <summary>
/// Strongtyped view of Godot's get_method_list / get_property_list output.
/// </summary>
public static class Reflector
{
/* ──────────── Typed records ──────────── */
public readonly record struct ArgInfo(
string Name,
Variant.Type Type,
PropertyHint Hint,
string HintString,
Variant DefaultValue);
public readonly record struct MethodInfoEx(
string Name,
IReadOnlyList<ArgInfo> Args,
IReadOnlyList<Variant> DefaultArgs,
MethodFlags Flags,
int Id,
ArgInfo? ReturnValue);
public readonly record struct PropertyInfoEx(
string Name,
string ClassName,
Variant.Type Type,
PropertyHint Hint,
string HintString,
PropertyUsageFlags Usage);
/* ──────────── Public helpers ──────────── */
public static List<MethodInfoEx> GetMethodsListEx(this GodotObject godotObject)=>GetMethods(godotObject);
public static List<MethodInfoEx> GetMethods(GodotObject obj)
{
var raw = obj.GetMethodList();
var list = new List<MethodInfoEx>(raw.Count);
foreach (Dictionary dict in raw)
{
// — Parse args —
var argsRaw = (Array)dict["args"];
var args = new List<ArgInfo>(argsRaw.Count);
foreach (Dictionary a in argsRaw)
args.Add(ParseArg(a));
// — Parse return (may be empty) —
ArgInfo? ret = null;
if (dict.TryGetValue("return", out var retRaw) && retRaw.AsGodotDictionary() is Dictionary rd && rd.Count > 0)
ret = ParseArg(rd);
list.Add(new MethodInfoEx(
Name: (string)dict["name"],
Args: args,
DefaultArgs: (Array)dict["default_args"],
Flags: (MethodFlags)(int)dict["flags"],
Id: (int)dict["id"],
ReturnValue: ret
));
}
return list;
}
public static bool TryGetMethodInfo(this GodotObject godotObject, string property, out MethodInfoEx info){
foreach (var item in GetMethods(godotObject))
{
if (item.Name == property){
info = item;
return true;
}
}
info = default;
return false;
}
public static List<PropertyInfoEx> GetPropertyListEx(this GodotObject godotObject)=>GetProperties(godotObject);
public static List<PropertyInfoEx> GetProperties(GodotObject obj)
{
var raw = obj.GetPropertyList();
var list = new List<PropertyInfoEx>(raw.Count);
foreach (Dictionary dict in raw)
{
list.Add(new PropertyInfoEx(
Name: (string)dict["name"],
ClassName: (string)dict["class_name"],
Type: (Variant.Type)(int)dict["type"],
Hint: (PropertyHint)(int)dict["hint"],
HintString: (string)dict["hint_string"],
Usage: (PropertyUsageFlags)(int)dict["usage"]
));
}
return list;
}
public static PropertyInfoEx GetProperty(this IEnumerable<PropertyInfoEx> properties, string name)=>properties.FirstOrDefault(item=>item.Name == name);
public static bool TryGetPropertyInfo(this GodotObject godotObject, string property, out PropertyInfoEx info){
foreach (var item in GetProperties(godotObject))
{
if (item.Name == property){
info = item;
return true;
}
}
info = default;
return false;
}
/* ──────────── Internals ──────────── */
private static ArgInfo ParseArg(Dictionary d) => new(
Name: (string)d["name"],
Type: (Variant.Type)(int)d["type"],
Hint: (PropertyHint)(int)d["hint"],
HintString: (string)d["hint_string"],
DefaultValue: d.TryGetValue("default_value", out var dv) ? dv : default);
}

View File

@@ -0,0 +1 @@
uid://bis0ef0hnuxin

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Godot;
using Godot.Collections;
namespace SJK.GodotHelpers;
public static class VariantUtils
{
public static Variant SafeToVariant(object value)
{
return value switch
{
null => new Variant(),
bool b => Variant.From(b),
int i => Variant.From(i),
long l => Variant.From((int)l), // Godot Variant only supports 32-bit int
float f => Variant.From(f),
double d => Variant.From((float)d),
string s => Variant.From(s),
Vector2 v2 => Variant.From(v2),
Vector2I v2i => Variant.From(v2i),
Vector3 v3 => Variant.From(v3),
Vector3I v3i => Variant.From(v3i),
Vector4 v4 => Variant.From(v4),
Vector4I v4i => Variant.From(v4i),
Rect2 rect2 => Variant.From(rect2),
Rect2I rect2i => Variant.From(rect2i),
Quaternion q => Variant.From(q),
Basis basis => Variant.From(basis),
Transform2D t2d => Variant.From(t2d),
Transform3D t3d => Variant.From(t3d),
Color color => Variant.From(color),
Plane plane => Variant.From(plane),
Aabb aabb => Variant.From(aabb),
GodotObject go => Variant.From(go),
byte[] bytes => Variant.From(bytes),
StringName sn => Variant.From(sn),
NodePath np => Variant.From(np),
Callable call => Variant.From(call),
Signal sig => Variant.From(sig),
Dictionary dict => Variant.From(dict),
Godot.Collections.Array array => Variant.From(array),
_ => throw new InvalidCastException($"Unsupported type '{value?.GetType().FullName}' for Variant conversion.")
};
}
public static Type GetSystemType(this Variant.Type type) => type switch
{
Variant.Type.Nil => typeof(object),
Variant.Type.Bool => typeof(bool),
Variant.Type.Int => typeof(int),
Variant.Type.Float => typeof(float),
Variant.Type.String => typeof(string),
Variant.Type.Vector2 => typeof(Vector2),
Variant.Type.Vector2I => typeof(Vector2I),
Variant.Type.Rect2 => typeof(Rect2),
Variant.Type.Rect2I => typeof(Rect2I),
Variant.Type.Vector3 => typeof(Vector3),
Variant.Type.Vector3I => typeof(Vector3I),
Variant.Type.Vector4 => typeof(Vector4),
Variant.Type.Vector4I => typeof(Vector4I),
Variant.Type.Transform2D => typeof(Transform2D),
Variant.Type.Transform3D => typeof(Transform3D),
Variant.Type.Basis => typeof(Basis),
Variant.Type.Quaternion => typeof(Quaternion),
Variant.Type.Aabb => typeof(Aabb),
Variant.Type.Color => typeof(Color),
Variant.Type.Plane => typeof(Plane),
Variant.Type.StringName => typeof(StringName),
Variant.Type.NodePath => typeof(NodePath),
Variant.Type.Rid => typeof(Rid),
Variant.Type.Object => typeof(GodotObject),
Variant.Type.Callable => typeof(Callable),
Variant.Type.Signal => typeof(Signal),
Variant.Type.Dictionary => typeof(Godot.Collections.Dictionary),
Variant.Type.Array => typeof(Godot.Collections.Array),
_ => typeof(object)
};
}

View File

@@ -0,0 +1 @@
uid://b18a1dp6f8tsv

132
src/VoxelGrid/BeltPort.cs Normal file
View File

@@ -0,0 +1,132 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using SJK.Functional;
[Tool]
[Meta(typeof(IAutoNode))]
public partial class BeltPort : Node3D, IBeltPort {
public override void _Notification(int what) => this.Notify(what);
[Dependency] public IVoxelGridRegistry Grid => this.DependOn<IVoxelGridRegistry>();
[Export] public Direction Face { get; set; } = default!;
[Export] public int Width { get; set; } = default!;
[Export] public PortAccess Access { get; set; } = default!;
[Export] public Path3D Path { get; set; } = default!;
[Dependency] public IItemRenderer ItemRenderer => this.DependOn<IItemRenderer>();
public BeltPortProfile Profile => new(GridTransform3D.FromGodot(GlobalTransform), Width, Access);
[Dependency] public IItemTransferAnimator ItemTransferAnimator => this.DependOn<IItemTransferAnimator>(()=> new CurveItemTransfer(){Curve3D = Path.Curve,Tree = GetTree(), ItemRenderer = ItemRenderer, Transform3D = GlobalTransform});
public void OnResolved()
{
if (Engine.IsEditorHint())
{
return;
}
Grid.Register<IBeltPort>(this, [.. (this as IBeltPort).Points()]);
}
public override void _Process(double delta)
{
DebugDraw3D.DrawLine(GlobalPosition,GlobalTransform * Face.ToVector(), Colors.Red);
if (Engine.IsEditorHint())
{
return;
}
for (int i = 0; i < _itemsDummys.Count; i++)
{
_itemsDummys[i] = (_itemsDummys[i].i+(float)delta,_itemsDummys[i].beltItem);
GD.Print(_itemsDummys[i].i);
ItemRenderer.UpdateTransform(_itemsDummys[i].beltItem,GlobalTransform * Path.Curve.SampleBakedWithRotation(_itemsDummys[i].i));
if (_itemsDummys[i].i > Path.Curve.GetBakedLength())
{
_itemsDummys[i].beltItem.Dispose();
_itemsDummys.RemoveAt(i);
i--;
}
}
}
public bool CanAccept(IBeltItem item, LaneSpan laneSpan, float beltT)
{
return true;
throw new System.NotImplementedException();
}
List<(float i,IBeltItem beltItem)> _itemsDummys = [];
public bool TryInsert(IBeltItem item, LaneSpan laneSpan, float beltT)
{
var tween = ItemTransferAnimator.StartTransfer(new(item,beltT){LaneSpan = laneSpan}, () => GD.Print("Done"));
// GD.Print("gg "+laneSpan);
tween.TweenCallback(Callable.From(item.Dispose));
// GD.Print(laneSpan);
// GD.Print(item.ToString());
// _itemsDummys.Add((0,item));
// item.Dispose();
return true;
throw new System.NotImplementedException();
}
}
public interface IItemTransferAnimator
{
Tween StartTransfer(
ConveyorSlice item,
Action onFinished = null);
}
public class InstanceItemTransfer : IItemTransferAnimator
{
public SceneTree Tree = default!;
public Func<ConveyorSlice, bool> AcceptFunc = default!;
public Tween StartTransfer(ConveyorSlice item, Action onFinished = null)
{
AcceptFunc(item);
onFinished?.Invoke();
return Tree.CreateTween();
throw new NotImplementedException();
}
}
public class CurveItemTransfer : IItemTransferAnimator
{
public Curve3D Curve3D = default!;
public IItemRenderer ItemRenderer = default!;
public Func<ConveyorSlice, bool> AcceptFunc = default!;
public SceneTree Tree = default!;
public Transform3D Transform3D;
public Tween AnimateAlongCurve(
ConveyorSlice item,
Curve3D curve,
float duration,
IItemRenderer renderer)
{
float length = curve.GetBakedLength();
var tween = Tree.CreateTween();
tween.TweenMethod(
Callable.From<float>(t =>
{
//Transform is not being set when using update transform in a tween, but can set position
// renderer.UpdateTransform(item, Transform3D * curve.SampleBakedWithRotation(t),1/duration);
(renderer as TestItemRendered)._items[item.Item].Transform =Transform3D *Curve3D.SampleBakedWithRotation(t).Translated(-Curve3D.SampleBakedWithRotation(t).Basis.X * item.LaneSpan.Start);
// GD.PrintS(Transform3D * curve.SampleBakedWithRotation(t).Origin,1/duration,(renderer as TestItemRendered)._items[item].GlobalPosition);
}),
0f,
length,
duration
);
return tween;
}
public Tween StartTransfer(ConveyorSlice item, Action onFinished = null){
var tween = AnimateAlongCurve(item,Curve3D,1,ItemRenderer);
if (onFinished is not null){
tween.TweenCallback(Callable.From(onFinished));
}
return tween;
}
}

View File

@@ -0,0 +1 @@
uid://ee5aoxi8mjnw

View File

@@ -0,0 +1,114 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
[Meta(typeof(IAutoNode))]
public partial class ConveyorItemRender : Node
{
public override void _Notification(int what) => this.Notify(what);
[Export] protected Path3D Path3D { get; set; } = default!;
[Export] protected TestItemConveyor ItemConveyor { get; set; } = default!;
[Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn<IItemRenderer>();
private Chickensoft.Sync.Primitives.AutoList<ConveyorSlice>.Binding binding = default!;
public override async void _Ready()
{
base._Ready();
if (!ItemConveyor.IsNodeReady())
{
await ToSignal(ItemConveyor, Node.SignalName.Ready);
}
binding = ItemConveyor.Items.Items.Bind();
// binding.OnRemove(callback =>
// {
// Items.Remove(callback.Item);
// // GD.PrintS(callback.Item, callback.BeltT);
// // if (itemsRenders.TryGetValue(callback.Item, out var node))
// // {
// // GD.PrintS(callback.Item, callback.BeltT,node);
// // itemsRenders.Remove(callback.Item);
// // node.QueueFree();
// // }
// });
binding.OnAdd((i, v) =>
{
// itemsRenders[i.Item] = new MeshInstance3D(){Mesh = new BoxMesh()};
// AddSibling(itemsRenders[i.Item]);
// itemsRenders[i.Item].Transform = Path3D.Curve.SampleBakedWithRotation(i.BeltT);
// GD.Print($"Item : {i},{v} added");
Items.UpdateTransform(i.Item,Path3D.GlobalTransform *Path3D.Curve.SampleBakedWithRotation(i.BeltT));
});
binding.OnUpdate((a,b) =>
{
Items.UpdateTransform(a.Item, Path3D.GlobalTransform *Path3D.Curve.SampleBakedWithRotation(b.BeltT).Translated(-Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X*(b.LaneSpan.Start)));
return;
if (itemsRenders.TryGetValue(a.Item, out var node))
{
if (tweens.TryGetValue(a.Item, out var t))
{
t.Kill();
}
tweens[a.Item] = t = CreateTween();
t.TweenProperty(node, "transform",Path3D.Curve.SampleBakedWithRotation(b.BeltT), .25f);
// node.Transform = Path3D.Curve.SampleBakedWithRotation(b.BeltT);
}
});
}
private Dictionary<IBeltItem, Node3D> itemsRenders = [];//This would be an item server for reuse via depency
private Dictionary<IBeltItem, Tween> tweens = [];//This would be an item server for reuse via depency
protected override void Dispose(bool disposing)
{
binding.Dispose();
base.Dispose(disposing);
}
}
public interface IItemRenderer
{
// Node3D GetVisualNode(IBeltItem beltItem);
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform);
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time);
void Remove(IBeltItem beltItem);
}
public partial class TestItemRendered : Node3D, IItemRenderer
{
public Dictionary<IBeltItem, Node3D> _items = [];
private ConditionalWeakTable<IBeltItem, Tween> _tweens = [];
public void Remove(IBeltItem beltItem)
{
if (_items.TryGetValue(beltItem, out var node)){
node.QueueFree();
}
_items.Remove(beltItem);
}
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time ){
if (!_items.TryGetValue(beltItem, out var node))
{
_items.Add(beltItem,node = beltItem.CreateItemVisual());
AddChild(node);
beltItem.Disposed += _ =>{node.QueueFree();_items.Remove(beltItem);};
node.Transform = newTransform;
return;
}
if (_tweens.TryGetValue(beltItem, out var tween))
{
tween.Kill();
_tweens.Remove(beltItem);
}
// GD.Print(newTransform);
tween = GetTree().CreateTween().BindNode(node);
tween.TweenProperty(node, "transform", newTransform, time);
_tweens.Add(beltItem,tween);
}
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => UpdateTransform(beltItem,newTransform,.25f);
}

View File

@@ -0,0 +1 @@
uid://bjuntmf2sjynp

View File

@@ -0,0 +1,83 @@
namespace ChickenGameTest;
using System;
using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
public interface IEquipment : IProvide<IEquipmentContext>//INode3D,
{
EquipmentId Id { get; set; }
}
[Tool]
[Meta(typeof(IAutoNode))]
public partial class Equipment() : Node3D, IEquipment
{
public override void _Notification(int what)
{
if (what == NotificationTransformChanged)
{
OnNotificationTransformChanged();
}
// if (Engine.IsEditorHint())
// {
// return;
// }
this.Notify(what);
}
public EquipmentId Id { get; set; } = new(Guid.NewGuid());
[Signal] public delegate void TickEventHandler();
[Export]public Vector3I GridPos { get; set; }
[Dependency] protected IVoxelGridQuery<LayeredEquipment> Grid => this.DependOn<IVoxelGridQuery<LayeredEquipment>>();
protected IEquipmentContext _equipmentContext { get; set; } = default!;
public override void _Ready()
{
this.SetNotifyTransform(true);
base._Ready();
// if (Engine.IsEditorHint())
// {
// return;
// }
_equipmentContext = new eqitpTest(this);
this.Provide();
Timer timer = new Timer(){WaitTime = .25f, Autostart = true};
AddChild(timer);
timer.Timeout += EmitSignalTick;
// timer.Timeout += () => Position += Vector3.One;
}
public void OnResolved()
{
GD.Print(Grid);
(Grid as EquipmentVoxelGrid).AddEquipment(GridPos, this);
GD.Print("Added Self");
}
IEquipmentContext IProvide<IEquipmentContext>.Value() => _equipmentContext;
public void OnNotificationTransformChanged()
{
GD.Print("Transform changed, now" , Position);
var newPos = new Vector3I(Mathf.RoundToInt(Position.X),Mathf.RoundToInt(Position.Y),Mathf.RoundToInt(Position.Z));
if (newPos == GridPos)
{
return;
}
GridPos = newPos;
// Transform = Transform.Origin = newPos;;
}
}
public record EquipmentId(Guid Id);
public interface IEquipmentContext
{
Equipment GetEquipment();
EquipmentId GetEquipmentId();
}
public record eqitpTest(Equipment Equipment) : IEquipmentContext
{
public Equipment GetEquipment() => Equipment;
public EquipmentId GetEquipmentId() => Equipment.Id;
}

View File

@@ -0,0 +1 @@
uid://bhh1c4a5gep6o

View File

@@ -0,0 +1,788 @@
namespace ChickenGameTest;
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 System.Diagnostics.CodeAnalysis;
public interface IMovementConveyor
{
IBeltPort StartPort { get; }
// IBeltSlotProfile StartPort { get; }
IBeltPort EndPort { get; }
// IBeltSlotProfile EndPort { get; }
// IOption<IBeltSlotProfile> InputPort { get; }
// IOption<IBeltSlotProfile> OutputPort { get; }
// IEnumerable<IBeltSlotProfile> GetPorts();
IEnumerable<IBeltPort> GetPorts();
IAutoValue<float> SpeedValue { get; }
float SpeedMagnitude { get; set; }
float SignedSpeed { get; set; }
bool IsReversed { get; set; }
float Length {get;set;}
// float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance);
// IBeltSlotProfile GetPortFacingStart();
// IBeltSlotProfile GetPortFacingEnd();
ItemConveyor.BeltDirection GetBeltDirection();
// Option<ConveyorSlice> GetItemTowardStart();
// Option<ConveyorSlice> GetItemTowardEnd();
// Option<ConveyorSlice> GetItemTowardInput();
// Option<ConveyorSlice> GetItemTowardOutput();
IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd();
IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart();
ItemConveyor.IBeltMovement GetMovementPolicy();
// IList<ConveyorSlice> Items { get; }
IOption<IBeltPort> GetPortFacing(IBeltPort slot);
BeltObstacle GetDistanceToNextItem(ItemConveyor.BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet<IMovementConveyor>? visted = null);
ConveyorPort CreatePort(BeltPortProfile profile, ItemConveyor.BeltT beltT, LaneSpan laneSpan);
}
public readonly struct ConveyorItemHandle
{
private readonly Action _remove;
private readonly Action<ConveyorSlice> _replace;
public int Index { get; }
public ConveyorSlice Slice { get; }
public IBeltItem Item => Slice.Item;
public LaneSpan Span => Slice.LaneSpan;
public float BeltT => Slice.BeltT;
public ConveyorItemHandle(int index, ConveyorSlice slice, Action remove, Action<ConveyorSlice> replace)
{
Index = index;
Slice = slice;
_remove = remove;
_replace = replace;
}
public void Remove() => _remove();
public void Replace(ConveyorSlice slice) => _replace(slice);
}
[Meta(typeof(IAutoNode))]
public partial class ItemConveyor : Node, IVoxelNode
{
// public record ItemPair(IBeltItem Item, float BeltT);
public override void _Notification(int what) => this.Notify(what);
[Export] public int Length { get; set; } = 1;
// [Export] public float Speed { get; set; } = .05f;
private readonly AutoValue<float> _speed = new(.05f);
public IAutoValue<float> Speed => _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 => Mathf.Sign(_speed.Value) < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); }
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
[Dependency] public IBeltMovement MovementSystem => this.DependOn<IBeltMovement>(() => new IndividualMovement());
public const float ITEMSIZE = .2f;
private const float ITEMHALFSIZE = ITEMSIZE / 2f;
//Basci port for simple conveyor, asuming no rever,
private List<ConveyorPort> _ports = [
new(){//EjectFace
Face = Direction.Front,
Direction= PortAccess.InOut,
LocalOffset = Vector3I.Zero,
BeltT = new BeltTEnd(ConveyorEnd.End),
PullPush = TransferMode.PushPull,
},
new(){//PullFace
Face = Direction.Back,
Direction= PortAccess.InOut,
LocalOffset = Vector3I.Zero,
BeltT = new BeltTEnd(ConveyorEnd.Start),
PullPush = TransferMode.PushPull//Or none, PlateUp has grtabber and none grabby varietns
},
new(){//PullFace
Face = Direction.Right,
Direction= PortAccess.BiDirectional,
LocalOffset = Vector3I.Zero,
BeltT = new BeltTOffset(.5f),
},
new(){//PullFace
Face = Direction.Left,
Direction= PortAccess.BiDirectional,
LocalOffset = Vector3I.Zero,
BeltT = new BeltTOffset(.5f),
},
new(){//PullFace
Face = Direction.Up,
Direction= PortAccess.BiDirectional,
LocalOffset = Vector3I.Zero,
BeltT = new BeltTOffset(.5f),
}
];
public ConveyorPort GetInputPort() => _ports.First(item => (item.PullPush & TransferMode.Pull)>0 && item.BeltT == (SignedSpeed >= 0 ? new BeltTEnd(ConveyorEnd.Start):new BeltTEnd(ConveyorEnd.End)));//Cache this
public ConveyorPort GetOutputPort() => _ports.First(item => (item.PullPush & TransferMode.Push)>0 && item.BeltT == (SignedSpeed < 0 ? new BeltTEnd(ConveyorEnd.Start):new BeltTEnd(ConveyorEnd.End)));//Cache this
public ConveyorPort GetStartPort() => _ports.First(item => item.BeltT == new BeltTEnd(ConveyorEnd.Start));
public ConveyorPort GetEndPort() => _ports.First(item => item.BeltT == new BeltTEnd(ConveyorEnd.End));
// public ConveyorPort GetPortInDirectionOfTravel() => Speed >= 0 ? GetOutputPort() : GetInputPort();
//needs better name
// private IEnumerable<ConveyorPort> ActivePorts(TransferMode p) => _ports.Where(item => item.PullPush == p);
public void OnResolved()
{
// GD.Print(SlotExtesion.MapSlotToFacingSlot(Vector3I.Zero,Direction.Back,2,new(2,0,1),Direction.Front,3));
// GD.Print(SlotExtesion.MapSlotToFacingSlot(new(2,0,1),Direction.Front,3,Vector3I.Zero,Direction.Back,2));
GridRegistry.Register(this);
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST
AddChild(timer);//TEST
timer.Timeout += OnTick;//TEST
for (int i = 0; i < _ports.Count; i++)
{
var port = _ports[i];
if (port.Access.HasFlag(PortAccess.In)){
port.AcceptItemFunc = (item, belt) =>
{
GD.PrintS("============================",port.Access,item,belt,port.BeltT);
if (port.BeltT is BeltTEnd end)
{
return TryInsertAtEnd(end.End, item);
}
if (port.BeltT is BeltTOffset offset && TryFindLocalInsertion(offset.T,ITEMHALFSIZE,out var point))
{
InsertInMiddleWithoutCheck(item, point);
return true;
}
return false;
};
_ports[i] = port;
}
}
// MovementSystem.OnItemMoved += static (item, segments, delta) => GD.PrintS(item, string.Join(',', segments.ToArray().Select(i => $"({i.Conveyor},{i.StartT},{i.EndT}")));
}
public override void _ExitTree()
{
base._ExitTree();
GridRegistry.UnRegister(Id);
}
public void OnTick()//TEMP METHOOD FOR TESTING
{
if (Input.IsActionPressed("ui_up"))
{
SignedSpeed = -SignedSpeed;
}
if (VoxelPosition == Vector3I.Forward*-2 && TryInsertAtEnd(ConveyorEnd.Start, new TestItem(){Height = 1, Width = 1, Temp = 1}))
InsertAtEndWithoutCheck(ConveyorEnd.Start,new TestItem(){Height = 1, Width = 1, Temp = 1});
MovementSystem.AdvanceBelt(this,1f);
return;
// if (_items.Count <= 0)
// {
// return;
// }
// if (Speed == 0)
// {
// return;
// }
// GD.Print("Thing: " + Speed + " "+ GetConveyorPortFacing(GetOutputPort()));
// var distToEnd = DistanceFromEnd(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
// var maxSpeed = Mathf.Min(Mathf.Abs(Speed), distToEnd) * Mathf.Sign(Speed);
// if (maxSpeed == 0)
// {
// return;
// }
// // GD.Print()
// for (int i = 0; i < _items.Count; i++)
// {
// _items[i] = _items[i] with { BeltT = Mathf.Clamp(_items[i].BeltT + maxSpeed, 0, Length) };
// GD.Print($"Item:{_items[i].Item}, Position:{_items[i].BeltT}");
// }
// // _items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
// var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
// // GD.Print(list.Count);
// foreach (var item in list)
// {
// GD.Print($"Item:{item.Item}, Position:{item.BeltT}");
// //TEST
// var v = GetConveyorPortFacing(GetOutputPort());
// if (v.HasValue)
// {
// GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
// if(v.Value.Conveyor.TryInsertAtEnd((v.Value.Port.BeltT as BeltTEnd).End, item.Item))
// {
// GD.Print("Item Moved");
// _items.Remove(item);
// }
// }
// //Move Item Into Other COnveyor with offset, this means a item could therolitcly move two the end of another conveyor, but need to decide if that should keep moving recurively, opr make items only move one conveyor max at atime
// }
}
public float? GetAvailableTravelForFrontItem(bool accountForNextConveyor = false) => GetBeltDirection() switch
{
BeltDirection.TowardStart => AnyItems() ? GetAvailableTravel(0, BeltDirection.TowardStart, accountForNextConveyor) : null,
BeltDirection.TowardEnd => AnyItems() ? GetAvailableTravel(ItemsCount - 1, BeltDirection.TowardEnd, accountForNextConveyor) : null,
BeltDirection.NotMoving => null,
_ => throw new NotSupportedException(),
};
public float GetAvailableSpaceFromEnd(ConveyorEnd end) => end switch
{
ConveyorEnd.Start => AnyItems()?_items[0].BeltT - ITEMSIZE:Length -ITEMHALFSIZE,
ConveyorEnd.End => AnyItems()?_items[^1].BeltT + ITEMSIZE : ITEMHALFSIZE,
_ => throw new NotSupportedException(),
};
public float GetAvailableTravel(
int itemIndex,
BeltDirection direction,
bool accountForNextConveyor = false
)
{
if (itemIndex < 0 || itemIndex >= _items.Count)
throw new IndexOutOfRangeException(
$"{nameof(itemIndex)}={itemIndex}, Count={_items.Count}"
);
var item = _items[itemIndex];
// Determine neighbor and boundary
bool towardEnd = direction == BeltDirection.TowardEnd;
float limit;
if (towardEnd)
{
var other = GetConveyorPortFacing(GetEndPort());
// Next item or belt end
limit = (itemIndex + 1 < _items.Count)
? _items[itemIndex + 1].BeltT - ITEMSIZE
: Length + (accountForNextConveyor && other.HasValue && other.Value.Port.BeltT is BeltTEnd end ? other.Value.Conveyor.GetAvailableSpaceFromEnd(end.End) : -ITEMHALFSIZE);
}
else
{
var other = GetConveyorPortFacing(GetStartPort());
// Previous item or belt start
limit = (itemIndex - 1 >= 0)
? _items[itemIndex - 1].BeltT + ITEMSIZE
: accountForNextConveyor && other.HasValue && other.Value.Port.BeltT is BeltTEnd end ? other.Value.Conveyor.GetAvailableSpaceFromEnd(end.End) : ITEMHALFSIZE;
}
float available = towardEnd
? limit - item.BeltT
: item.BeltT - limit;
return Mathf.Max(0f, available);
}
public enum ConveyorEnd { Start, End }
public static ConveyorEnd SwapEnd(ConveyorEnd end) => end switch
{
ConveyorEnd.Start => ConveyorEnd.End,
ConveyorEnd.End => ConveyorEnd.Start,
_ => throw new NotSupportedException($"{end}"),
};
public float DistanceFromEnd(ConveyorEnd end) => end switch
{
ConveyorEnd.Start => _items.Count == 0 ? Length : _items[0].BeltT,
ConveyorEnd.End => _items.Count == 0 ? Length : Length - _items[^1].BeltT,
_ => throw new NotSupportedException($"{end}"),
};
public float GetTravelDistanceInDirection(BeltDirection direction) => direction switch
{
BeltDirection.TowardEnd => _items.Count == 0 ? Length : _items[0].BeltT,
BeltDirection.TowardStart => _items.Count == 0 ? Length : Length - _items[^1].BeltT,
BeltDirection.NotMoving => 0,
_ => throw new NotSupportedException($"{direction}"),
};
public enum BeltDirection : sbyte {
TowardStart = -1,
NotMoving = 0,
TowardEnd = 1
}
public bool TryFindLocalInsertion(
float beltT,
float maxDistance,
out float resultT
)
{
throw new NotImplementedException();
// float minAllowed = Mathf.Max(ITEMHALFSIZE, beltT - maxDistance);
// float maxAllowed = Mathf.Min(Length - ITEMHALFSIZE, beltT + maxDistance);
// if (minAllowed > maxAllowed)
// {
// resultT = default;
// return false;
// }
// if (_items.Count == 0)
// {
// resultT = Mathf.Clamp(beltT, minAllowed, maxAllowed);
// return true;
// }
// int index = _items.BinarySearch(
// new(null!, beltT),
// Comparer<ConveyorSlice>.Create(
// (a, b) => a.BeltT.CompareTo(b.BeltT)
// )
// );
// if (index < 0)
// {
// index = ~index;
// }
// float gapMin = index > 0
// ? _items[index - 1].BeltT + ITEMSIZE
// : ITEMHALFSIZE;
// float gapMax = index < _items.Count
// ? _items[index].BeltT - ITEMSIZE
// : Length - ITEMHALFSIZE;
// // Intersect gap with allowed window
// gapMin = Mathf.Max(gapMin, minAllowed);
// gapMax = Mathf.Min(gapMax, maxAllowed);
// if (gapMin > gapMax)
// {
// resultT = default;
// return false;
// }
// resultT = Mathf.Clamp(beltT, gapMin, gapMax);
// return true;
}
public BeltDirection GetBeltDirection() => _speed.Value switch
{
0 => BeltDirection.NotMoving,
> 0 => BeltDirection.TowardEnd,
< 0 => BeltDirection.TowardStart,
_ => throw new NotSupportedException($"{nameof(Speed)} with value {Speed} is not Supported")
};
/// <summary>
/// Returns the Item that would be Leading the Belt based off the direction of the belt, or null if no items or Speed is zero.
/// </summary>
/// <returns>The Leading Item</returns> <summary>
///
/// </summary>
/// <returns></returns>
public ConveyorSlice? GetFirstItem() => GetBeltDirection() switch
{
BeltDirection.TowardStart => _items.Any() ? _items[0] : null,
BeltDirection.TowardEnd => _items.Any() ? _items[^1] : null,
BeltDirection.NotMoving => null,
_ => null
};
/// <summary>
/// Returns the Last item that would be oppsite the Leading Item, or null if there is no items or Speed is zero.
/// </summary>
/// <returns>The last Item in Secuance</returns> <summary>
///
/// </summary>
/// <returns></returns>
public ConveyorSlice? GetLastItem() => GetBeltDirection() switch
{
BeltDirection.TowardStart => _items.Any() ? _items[0] : null,
BeltDirection.TowardEnd => _items.Any() ? _items[^1] : null,
BeltDirection.NotMoving => null,
_ => null
};
public bool AnyItems() => _items.Any();
public int ItemsCount => _items.Count;
private void InsertInMiddleWithoutCheck(IBeltItem item, float beltT)
{
throw new NotImplementedException();
_items.Add(new(item, beltT));
// _items.Sort(Comparer<ConveyorSlice>.Create(
// (a, b) => a.BeltT.CompareTo(b.BeltT)));
}
// public float SpaceToInsertFromEnd(ConveyorEnd end)//Needs to know if belt space exists in next/previous
// {
// var distToEnd = DistanceFromEnd(end);
// var other = GetConveyorPortFacing(GetOutputPort());
// if (!other.HasValue || other.Value.Port.BeltT != new BeltTEnd(SwapEnd(end)) || other.Value.Port.Direction == PortAccess.Out)
// {
// return Mathf.Max(0, distToEnd - ITEMHALFSIZE);
// }
// return distToEnd + other.Value.Conveyor.DistanceFromEnd(SwapEnd(end));//Should Be space to inset, but needs to be serpate into 2 functons to prevent stgackoverflow
// }
public bool TryInsertAtEnd(ConveyorEnd end, IBeltItem itemStack)
{
var space = DistanceFromEnd(end);
if (space >= ITEMHALFSIZE)
{
InsertAtEndWithoutCheck(end, itemStack);
return true;
}
return false;
}
private void InsertAtEndWithoutCheck(ConveyorEnd end, IBeltItem item, float offset = 0)
{
ConveyorSlice itemPair = new(item, end switch { ConveyorEnd.Start => offset, ConveyorEnd.End => Length - offset, _ => throw new NotSupportedException() });
if (end == ConveyorEnd.Start)
{
_items.Insert(0, itemPair);
}
else
{
_items.Add(itemPair);
}
}
private (ItemConveyor Conveyor, ConveyorPort Port)? GetConveyorPortFacing(ConveyorPort port, Predicate<ConveyorPort>? predicate = default)
{
predicate ??= _ => true;
var pos = VoxelPosition + port.LocalOffset + port.Face.ToVector();
var others = GridRegistry.Get<ItemConveyor>(pos);
foreach (var item in others)
{
var otherPorts = item._ports.Where(otherPort => otherPort.Face.Reverse() == port.Face && predicate(otherPort));
GD.Print("ports "+string.Join(',',otherPorts.Select(i=>i.BeltT)));
if (otherPorts.Any())
{
return (item, otherPorts.First());
}
}
return null;
}
public Guid Id { get; set; }
[Export]
public Vector3I VoxelPosition { get; set; }
[Export]
public Direction VoxelRotation { get; set; } = Direction.Front;//test
public IEnumerable<Vector3I> Shape => [Vector3I.Zero];
private readonly AutoList<ConveyorSlice> _items = [];
public IAutoList<ConveyorSlice> Items => _items;
public struct ConveyorPort : IBeltSlotProfile// May chagne to record
{
public Vector3I LocalOffset;
public BeltT BeltT;
public Direction Face;
public TransferMode PullPush;
public PortAccess Direction;
public Func<IBeltItem, float, bool>? AcceptItemFunc;
public Func<IBeltItem, float, bool>? CanAcceptItemFunc;
public IMovementConveyor MovementConveyor;
public readonly Vector3I Position => LocalOffset;
public readonly int Width => 1;
public readonly PortAccess Access => Direction;
public Guid Id => throw new NotImplementedException();
public Vector3I VoxelPosition => LocalOffset;
public Direction VoxelRotation => throw new NotImplementedException();
public IEnumerable<Vector3I> Shape => [new Vector3I(0, 0, 0)];
// readonly Direction IBeltSlotProfile.Direction => Face;
[MemberNotNullWhen(true,nameof(CanAcceptItemFunc))]
[MemberNotNullWhen(true,nameof(AcceptItemFunc))]
public readonly bool CanAcceptItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0)
{
if (CanAcceptItemFunc is null || AcceptItemFunc is null || !Access.HasFlag(PortAccess.In) )//|| LaneSpan.Encapsulates(new(0, (ushort)Width), laneSpan))
{
return false;
}
return CanAcceptItemFunc(beltItem, beltT);
}
public readonly bool TryInsertItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0)
{
if (!CanAcceptItem(beltItem, laneSpan, beltT))
{
return false;
}
return AcceptItemFunc(beltItem, beltT);
}
}
public abstract record BeltT();
public record BeltTEnd(ConveyorEnd End) : BeltT();
public record BeltTOffset(float T) : BeltT();
// public enum PortAccess : byte
// {
// In,
// Out,
// InOut,
// BiDirectional = InOut
// }
// [Flags]
// public enum TransferMode : byte//Need Better Name
// {
// None = 0,
// Push = 1,
// Pull = 2,
// PushPull = Push | Pull
// }
public interface IBeltMovement
{
void AdvanceBelt(ItemConveyor conveyor, float delta);
void AdvanceBelt(IMovementConveyor conveyor, float delta);
// event ItemMoved? OnItemMoved;
// delegate void ItemMoved(
// IBeltItem item,
// ReadOnlySpan<ItemMovementSegment> segments,
// float deltaTime
// );
}
public sealed class IndividualMovement : IBeltMovement
{
public void AdvanceBelt(ItemConveyor conveyor, float delta)
{
if (!conveyor.AnyItems())
{
return;
}
var beltDirection = conveyor.GetBeltDirection();
if (beltDirection == BeltDirection.NotMoving)
{
return;
}
var towardStart = beltDirection == BeltDirection.TowardStart;
var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
if (towardStart)
{
for (int i = 0; i < conveyor.ItemsCount; i++)
{
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
if (maxSpeed == 0)
{
return;
}
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
if (endT <= 0)
{
// segments.Add(new(next.Value.Conveyor, ));
if (next.Value.Port.TryInsertItem(conveyor._items[i].Item, LaneSpan.One))
{
GD.Print("Item Moved");
conveyor._items.Remove(conveyor._items[i]);
i--;
}
}
}
}
else
{
for (int i = conveyor.ItemsCount-1; i >=0 ; i--)
{
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
if (maxSpeed == 0)
{
return;
}
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
if (endT >= conveyor.Length)
{
// segments.Add(new(next.Value.Conveyor, ));
if (next.Value.Port.TryInsertItem(conveyor._items[i].Item, LaneSpan.One))
{
GD.Print("Item Moved");
conveyor._items.Remove(conveyor._items[i]);
i--;
}
}
}
}
}
public void AdvanceBelt(IMovementConveyor conveyor, float delta)
{
// GD.Print(conveyor.Items);
if (conveyor.GetBeltDirection() == BeltDirection.NotMoving)
{
return;
}
// GD.Print("hello");
var towardStart = conveyor.GetBeltDirection() == BeltDirection.TowardStart;
foreach (var itemRef in towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd())
{
float maxMove = conveyor.SpeedMagnitude * delta;
var space = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),itemRef.Value.BeltT,maxMove,itemRef.Value.LaneSpan);
// space -= ITEMSIZE;//AcountForSPacing
// if ()
// {
// continue;
// }
if (space is ItemBeltObstacle itemOb && itemOb.Distance <= 0)
{
continue;
}
float amountToMove = Mathf.Min(space.Distance, maxMove);
itemRef.Replace(itemRef.Value with { BeltT = itemRef.Value.BeltT + amountToMove * Mathf.Sign(conveyor.SignedSpeed)});
// GD.PrintS(conveyor.Items.Count,space,amountToMove," "+ itemRef.BeltT,itemRef.Item,conveyor.GetBeltDirection(),towardStart);
if (towardStart ? itemRef.Value.BeltT <= 0 : itemRef.Value.BeltT >= conveyor.Length)
{
var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
// GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
if (facing.HasValue(out var slot) && slot.TryInsert(itemRef.Value.Item, space is PortBeltObstacle portO ? portO.LaneSpan : itemRef.Value.LaneSpan, 0))
{
// GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
// GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
// GD.Print(itemRef.Item);
itemRef.Remove();
}
}
}
}
}
public sealed class StrictMovement : IBeltMovement
{
// public event IBeltMovement.ItemMoved? OnItemMoved;
public void AdvanceBelt(ItemConveyor conveyor, float delta)
{
if (!conveyor.AnyItems())
{
return;
}
var beltDirection = conveyor.GetBeltDirection();
if (beltDirection == BeltDirection.NotMoving)
{
return;
}
var towardStart = beltDirection == BeltDirection.TowardStart;
// GD.Print("Thing: " + Speed + " "+ conveyor.GetConveyorPortFacing(conveyor.GetOutputPort()));
// var moveItemsIntoNextBelt = false;
var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
// return;
if (!possibleItemTravel.HasValue)
{
//There is no items, this should not be called
throw new NotSupportedException("This should not e possible if there is no items or the belt is not moving, it should have returend before");
}
// if (next.HasValue && next.Value.Port.BeltT is BeltTEnd beltTEnd && (next.Value.Port.Direction == PortAccess.In || next.Value.Port.Direction == PortAccess.BiDirectional))
// {
// possibleItemTravel += next.Value.Conveyor.DistanceFromEnd(beltTEnd.End);
// moveItemsIntoNextBelt = true;
// }
// var itemPair = conveyor.GetFirstItem();
// var distToEnd = conveyor.GetAvailableTravel(conveyor.ItemsCount - 1, beltDirection);//(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
if (maxSpeed == 0)
{
// var fI = conveyor.GetAvailableTravelForFrontItem();
// if (fI.HasValue && fI.Value <= 0 && next.Value.Port.TryInsertItem(conveyor.GetFirstItem().Value.Item, LaneSpan.One))
// {
// GD.Print("Item Moved");
// conveyor._items.Remove(conveyor.GetFirstItem().Value);
// }
return;
}
List<ConveyorSlice> toMove = [];
// Dictionary<IBeltItem, List<ItemMovementSegment>> moves = [];
for (int i = 0; i < conveyor.ItemsCount; i++)
{
// List<ItemMovementSegment> segments = [];
// var startT = conveyor._items[i].BeltT;
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
// segments.Add(new(conveyor, startT, towardStart? Mathf.Max(endT, 0) : Mathf.Min(endT, conveyor.Length),Mathf.Abs(maxSpeed)));
if (towardStart ? (endT <= 0) : endT >= conveyor.Length)
{
toMove.Add(conveyor._items[i]);
// segments.Add(new(next.Value.Conveyor, ));
}
// moves.Add(conveyor._items[i].Item, segments);
GD.Print($"Item: {conveyor._items[i].Item}, Position:{endT}");
}
// conveyor._items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
// var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
// GD.Print(list.Count);
if (next.HasValue)
{
foreach (var item in toMove)
{
GD.Print($"Item: {item.Item}, Position:{item.BeltT} -----------------");
//TEST
// var v = GetConveyorPortFacing(GetOutputPort());
// if (v.HasValue)//Should be true if items are > Length
// {
// GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
// var end = (next.Value.Port.BeltT as BeltTEnd).End;
// next.Value.Conveyor.InsertAtEndWithoutCheck(end, item.Item);
if (next.Value.Port.TryInsertItem(item.Item, item.LaneSpan))
{
GD.Print("Item Moved");
conveyor._items.Remove(item);
}
}
}
}
public void AdvanceBelt(IMovementConveyor conveyor, float delta) => throw new NotImplementedException();
}
}
public struct ConveyorSlice(IBeltItem item, float beltT = 0)
{
public IBeltItem Item = item;
public float BeltT = beltT;
public LaneSpan LaneSpan = LaneSpan.One;
public override string ToString() => $"{Item}, {BeltT}, {LaneSpan}";
}
public readonly struct LaneSpan : IEquatable<LaneSpan>
{
public readonly ushort Start;
public readonly ushort End;
public readonly int Width => End - Start;
public static readonly LaneSpan One = new(0, 1);
public static readonly LaneSpan Zero = new(0, 0);
public LaneSpan(ushort start, ushort end)
{
Start = start;
End = end;
}
// [MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static bool OverLaps(LaneSpan a, LaneSpan b) => a.Start < b.End && b.Start < a.End;
// [MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static LaneSpan Shifted(LaneSpan a, int amount) => checked(new LaneSpan((ushort)(a.Start + amount), (ushort)(a.End + amount)));
public static bool Encapsulates(LaneSpan a, LaneSpan b) => b.Start>=a.Start && b.End<= b.End;
public override string ToString() => $"(Start:{Start}, End:{End})";
public bool Equals(LaneSpan other) => Start == other.Start && End == other.End;
public static bool operator ==(LaneSpan a, LaneSpan b) => a.Equals(b);
public static bool operator !=(LaneSpan a, LaneSpan b) => !a.Equals(b);
}
public static class Extersions
{
public static ItemConveyor.BeltDirection DirectionTo(this ItemConveyor.ConveyorEnd end) => end switch
{
ItemConveyor.ConveyorEnd.Start => ItemConveyor.BeltDirection.TowardStart,
ItemConveyor.ConveyorEnd.End => ItemConveyor.BeltDirection.TowardEnd,
_ => throw new NotImplementedException(),
};
}

View File

@@ -0,0 +1 @@
uid://cx35jfqkjnou8

View File

@@ -0,0 +1,131 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Linq;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
[Meta(typeof(IAutoNode))]
public partial class SlotComponentNode
: Node3D , IEquipmentComponent
{
public override void _Notification(int what) => this.Notify(what);
[Export] public SlotDirection Direction;
[Export] public Vector3I LocalCellOffset;
[Dependency] protected IVoxelGridQuery<LayeredEquipment> Grid => this.DependOn<IVoxelGridQuery<LayeredEquipment>>();
[Dependency] protected IEquipmentContext EquipmentContext =>this.DependOn<IEquipmentContext>();
[Dependency] protected IEquipmentComponentRegistry ComponentRegistry => this.DependOn<IEquipmentComponentRegistry>();
public EquipmentId EquipmentId => EquipmentContext.GetEquipmentId();
// SO instead of using an hidden list of compeonts on the equimpent, have the equpment provide an reggistery that compnents depend on to register themselves to, and allow for muitple of the same type.
public void OnResolved()
{
GD.PrintS(EquipmentContext, Grid);
EquipmentContext.GetEquipment().Tick += Tick;
ComponentRegistry.Register(this);
GD.Print("Added Tick Listener");
}
private void Tick()
{
Grid.GetVoxel(EquipmentContext.GetEquipment().GridPos + LocalCellOffset/*TODO Acount for Direction*/).IfAny(e =>
{
var slots = e.SelectMany(ee => ee.GetChildren().OfType<SlotComponentNode>().Where(_=> true/*Where slot faces this one and is not output*/)).ToList();
// slots.First().AcceptItem(new TestItem());//For testing accuming adding item, should probly sort and cascade so progatrion stops when out of items, but should only be one result, as each componet is one face
});
// GD.Print("Tryied moveing items test");
}
public bool AcceptItem(IBeltItem itemStack)
{
return true;
}
public override void _ExitTree()
{
base._ExitTree();
EquipmentContext.GetEquipment().Tick -= Tick;
ComponentRegistry.Unregister(this);
}
// public Vector3I GetSlotCell() {
// return Equipment.GetOccupiedCell() + LocalCellOffset;
// }
// public Vector3 GetSlotWorldPosition() {
// return Grid.CellToWorld(GetSlotCell());
// }
}
public interface IEquipmentComponentRegistry {
void Register<T>(T component)
where T : class, IEquipmentComponent;
void Unregister<T>(T component)
where T : class, IEquipmentComponent;
T? Get<T>(EquipmentId id)
where T : class, IEquipmentComponent;
IReadOnlyList<T> GetAll<T>(EquipmentId id)
where T : class, IEquipmentComponent;
}public sealed class EquipmentComponentRegistry
: IEquipmentComponentRegistry {
private readonly Dictionary<
EquipmentId,
Dictionary<Type, List<object>>
> _map = new();
public void Register<T>(T component)
where T : class, IEquipmentComponent {
GD.Print(component);
if (!_map.TryGetValue(component.EquipmentId, out var types)) {
types = new();
_map[component.EquipmentId] = types;
}
var type = typeof(T);
if (!types.TryGetValue(type, out var list)) {
list = new();
types[type] = list;
}
list.Add(component);
}
public T? Get<T>(EquipmentId id)
where T : class, IEquipmentComponent {
if (_map.TryGetValue(id, out var types) &&
types.TryGetValue(typeof(T), out var list))
return list[0] as T;
return null;
}
public IReadOnlyList<T> GetAll<T>(EquipmentId id)
where T : class, IEquipmentComponent {
if (_map.TryGetValue(id, out var types) &&
types.TryGetValue(typeof(T), out var list))
return list.Cast<T>().ToList();
return Array.Empty<T>();
}
public void Unregister<T>(T component)
where T : class, IEquipmentComponent {
if (_map.TryGetValue(component.EquipmentId, out var types) &&
types.TryGetValue(typeof(T), out var list))
list.Remove(component);
}
}
public interface IEquipmentComponent {
EquipmentId EquipmentId { get; }
}

View File

@@ -0,0 +1 @@
uid://drsqsmj0bn1ob

250
src/VoxelGrid/SlotFace.cs Normal file
View File

@@ -0,0 +1,250 @@
namespace ChickenGameTest;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Chickensoft.AutoInject;
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using ChickenGameTest;
public interface ISlotFace : IVoxelNode // INode3D,
{
ISlotFace GetConnectingFace();
}
[Tool]
[Meta(typeof(IAutoNode))]
public partial class SlotFace : Node3D, ISlotFace
{
public Guid Id { get; set; }
public IEnumerable<Vector3I> Shape => [Vector3I.Zero];
[Export] public Vector3I VoxelPosition { get; set; }
[Export] public Direction VoxelRotation { get; set; }
[Export] public bool ShowDebug { get; set; } = false;
public override void _Notification(int what) => this.Notify(what);
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
public void OnResolved()
{
GridRegistry.Register(this);
GD.Print("Regestered");
}
public ISlotFace GetConnectingFace()//TODO there could be tecnaly muiple slots shareing a face, and should be acounted for
{
var nodes = GridRegistry.Get<ISlotFace>(VoxelPosition + VoxelRotation.ToVector());//TODO Acoount for rotation to get slot direction
// GD.Print(nodes.Count());
foreach (var item in nodes)
{
if (item.VoxelRotation.Reverse() == VoxelRotation)
{
return item;
}
}
return null;
}
private static Vector3[] _Square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
public override void _Process(double delta)
{
if (ShowDebug)
{
ShowDebugFace();
}
}
private void ShowDebugFace()
{
var color = Colors.Red;
var pos = ToGlobal(new Vector3(0, 0, -.5f));
if (!Engine.IsEditorHint())
{
color = GetConnectingFace() is null ? Colors.Red : Colors.Green;
}
// var text = $"SlotFace{Name}";
// GD.PrintS(color);
DebugDraw3D.DrawLinePath([.. _Square.Select(ToGlobal)],color);
// DebugDraw3D.DrawText(pos, text, 16);
}
}
public interface IVoxelGridRegistry
{
[Obsolete]
void Register<T>(T voxelNode) where T : IVoxelNode;
void Register<T>(T voxelNode, params Vector3I[] positions);
void UnRegister<T>(T voxelNode) where T : IVoxelNode=> UnRegister(voxelNode.Id);
void UnRegister<T>(T voxelNode, params Vector3I[] positions);
void UnRegister(Guid id);
IEnumerable<T> Get<T>(Vector3I voxelPos);
IEnumerable<T> Get<T>(params Vector3I[] voxelPos);
}
public sealed class VoxelRegistry : IVoxelGridRegistry
{
private Dictionary<Vector3I, ICollection<object>> _data;
public VoxelRegistry()
{
_data = new();
}
public IEnumerable<T> Get<T>(Vector3I voxelPos)
{
if (!_data.TryGetValue(voxelPos, out var entrys))
{
yield break;
}
foreach (var item in entrys.OfType<T>())
{
yield return item;
}
}
public IEnumerable<T> Get<T>(params Vector3I[] voxelPos)
{
foreach (var item in voxelPos)
{
foreach (var item2 in Get<T>(item))
{
yield return item2;
}
}
}
public void Register<T>(T voxelNode) where T : IVoxelNode
{
void register(Vector3I pos, T point)
{
if (!_data.TryGetValue(pos, out var entrys))
{
entrys = [];
_data[pos] = entrys;
}
entrys.Add(point);
}
voxelNode.Shape.ToList().ForEach(
item =>
{
register(item + voxelNode.VoxelPosition, voxelNode);//TODO Acount For Rotation
}
);
}
public void Register<T>(T voxelNode, params Vector3I[] positions)
{
void register(Vector3I pos, T point)
{
if (!_data.TryGetValue(pos, out var entrys))
{
entrys = [];
_data[pos] = entrys;
}
entrys.Add(point);
}
foreach (var pos in positions)
{
if (positions.Length>1){
GD.Print("hhhhhhhhh ",pos);}
register(pos, voxelNode);//TODO Acount For Rotation
}
}
public void UnRegister(Guid id) => throw new NotImplementedException();
public void UnRegister<T>(T voxelNode, params Vector3I[] positions)
{
void unRegister(Vector3I pos, T point)
{
if (!_data.TryGetValue(pos, out var entrys))
{
return;
}
entrys.Remove(point);
}
foreach (var item in positions)
{
unRegister(item, voxelNode);
}
}
}
public interface IVoxelNode
{
Guid Id { get; }
Vector3I VoxelPosition { get; }
Direction VoxelRotation { get; } //TODO replace with struct/record for static rotation typeing
IEnumerable<Vector3I> Shape { get; }//TOPO make SHape able to account for occpancy/ partial filled voxels.
}
public enum Direction
{
Up, Down, Left, Right, Front, Back
}
public static class DirectionExtession
{
public static Vector3I ToVector(this Direction direction) => direction switch
{
Direction.Up => Vector3I.Up,
Direction.Down => Vector3I.Down,
Direction.Left => Vector3I.Left,
Direction.Right => Vector3I.Right,
Direction.Front => Vector3I.Forward,
Direction.Back => Vector3I.Back,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
public static Direction Reverse(this Direction direction) => direction switch
{
Direction.Up => Direction.Down,
Direction.Down => Direction.Up,
Direction.Left => Direction.Right,
Direction.Right => Direction.Left,
Direction.Front => Direction.Back,
Direction.Back => Direction.Front,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
public static Direction RotateClockWise(this Direction direction) => direction switch
{
Direction.Up => Direction.Up,
Direction.Down => Direction.Down,
Direction.Left => Direction.Back,
Direction.Right => Direction.Front,
Direction.Front => Direction.Right,
Direction.Back => Direction.Left,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
public static Direction RotateCounterClockWise(this Direction direction) => direction switch
{
Direction.Up => Direction.Up,
Direction.Down => Direction.Down,
Direction.Right => Direction.Front,
Direction.Left => Direction.Back,
Direction.Back => Direction.Right,
Direction.Front => Direction.Left,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
}
public interface IStorage<T> where T : class
{
StorageDefinition Definition { get; }
IEnumerable<T> GetAllItems();
void Remove(T item);
bool TryInsert(T item);
record StorageDefinition(string Name, ItemType Type);
}
public record ItemType(string Name);
public partial class ItemStoragePool : Node, IStorage<IBeltItem>
{
[Export] public int MaxAmount { get; set; } = 5;
public IStorage<IBeltItem>.StorageDefinition Definition { get; set; } = default!;
private readonly List<IBeltItem> _items = [];
public IEnumerable<IBeltItem> GetAllItems() => _items;
public bool TryInsert(IBeltItem item)
{
if (_items.Count >= MaxAmount)
{
return false;
}
_items.Add(item);
return true;
}
public void Remove(IBeltItem item) => _items.Remove(item);
}

View File

@@ -0,0 +1 @@
uid://dao8u7hn0yadp

View File

@@ -0,0 +1,711 @@
namespace ChickenGameTest;
using Chickensoft.Introspection;
using Chickensoft.AutoInject;
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Chickensoft.Sync.Primitives;
using SJK.Functional;
public class Sorted1DList<T>
{
private readonly AutoList<T> _items = [];
#if DEBUG
private readonly AutoList<T>.Binding _binding;
#endif
private readonly Func<T, float> _getPosition;
public Sorted1DList(Func<T, float> getPos)
{
_getPosition = getPos;
// return;
#if DEBUG
_binding = _items.Bind();
_binding.OnUpdate((a, b, c) =>
{
// GD.PrintS("++++++++++++++++",a,b,c);
});
// return;
_binding.OnModify(() =>
{
for (int i = 1; i < _items.Count; i++)
{
if (_getPosition(_items[i - 1]) >= _getPosition(_items[i]))
{
throw new InvalidOperationException(
$"AutoList not sorted at index {i - 1} → {i}. ({_getPosition(_items[i-1])},{_getPosition(_items[i])})");
}
}
});
#endif
}
public int Count => _items.Count;
public IAutoList<T> Items => _items;
public IEnumerable<ItemHandle> EnumerateTowardEnd(int? startIndex = null)
{
if (startIndex.HasValue && startIndex >= Count)
{
throw new IndexOutOfRangeException($"{nameof(startIndex)}:{startIndex} can not be greater then or equal to {nameof(Count)}:{Count}");
}
for (int i = Mathf.Max(0,startIndex ?? 0); i < _items.Count; i++)
{
bool removed = false;
yield return new(_items[i],
() =>
{
if (removed)
{
throw new NotSupportedException($"{nameof(ItemHandle.Remove)} can only be called once per item.");
}
removed = true;
_items.RemoveAt(i);
i--;
},
(replaced) =>
{
if (removed)
{
throw new NotSupportedException("Can not Replace an Item after Removing it");
}
_items[i] = replaced;
}
);
}
}
public IEnumerable<ItemHandle> EnumerateTowardStart(int? startIndex = null)
{
if (startIndex.HasValue && startIndex < 0)
{
throw new IndexOutOfRangeException($"{nameof(startIndex)}:{startIndex} can not be less than zero");
}
for (int i = Math.Min(startIndex ?? (_items.Count - 1), _items.Count -1); i>=0 ; i--)
{
bool removed = false;
yield return new(_items[i],
() =>
{
if (removed)
{
throw new NotSupportedException($"{nameof(ItemHandle.Remove)} can only be called once per item.");
}
removed = true;
_items.RemoveAt(i);
// i--;
},
(replaced) =>
{
if (removed)
{
throw new NotSupportedException("Can not Replace an Item after Removing it");
}
if (i-1>=0 && _getPosition(_items[i-1]) >= _getPosition(replaced))
{
throw new Exception();
}
if (i+1<Count && _getPosition(_items[i+1]) <= _getPosition(replaced))
{
throw new Exception();
}
_items[i] = replaced;
}
);
}
}
public void Insert(T item)
{
float pos = _getPosition(item);
if (_items.Count == 0 || pos >= _getPosition(_items[^1]))
{
_items.Add(item);
return;
}
if (pos <=_getPosition(_items[0]))
{
_items.Insert(0, item);
return;
}
var index = LowerBound(pos);
_items.Insert(index, item);
}
private int LowerBound(float pos)
{
int lo = 0;
int hi = _items.Count;
while (lo < hi)
{
int mid = (lo + hi) >> 1;
if (_getPosition(_items[mid]) < pos)
{
lo = mid + 1;
}
else
{
hi = mid;
}
}
return lo;
}
public (Option<T> lower, Option<T> upper) GetNeighbors(float pos)
{
int index = LowerBound(pos);
var lower = index > 0 ? Option<T>.Some(_items[index - 1]) : Option<T>.None;
var upper = index < _items.Count ? Option<T>.Some(_items[index]) : Option<T>.None;
return (lower, upper);
}
public readonly struct ItemHandle
{
private readonly Action _remove;
private readonly Action<T> _replace;
public T Value { get; }
public ItemHandle(T value, Action remove, Action<T> replace)
{
Value = value;
_remove = remove;
_replace = replace;
}
public void Remove() => _remove();
public void Replace(T slice) => _replace(slice);
}
}
[Meta(typeof(IAutoNode))][Tool]
public partial class TestItemConveyor : Node, IMovementConveyor
{
public override void _Notification(int what) => this.Notify(what);
[Dependency] public ItemConveyor.IBeltMovement MovementSystem => this.DependOn<ItemConveyor.IBeltMovement>(() => new ItemConveyor.IndividualMovement());
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
// private readonly AutoList<ConveyorSlice> _items = [];
// public IAutoList<ConveyorSlice> Items => _items;
public readonly Sorted1DList<ConveyorSlice> Items = new(pos => pos.BeltT);
// private AutoList<ConveyorSlice>.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<IBeltPort> OtherPorts = [];
// [Export] public Vector3I Position { get; set; } = default!;
//Speed per unit time
private readonly AutoValue<float> _speed = new(1);
public IAutoValue<float> 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<ConveyorSlice> Items => _items;
public float Length { get; set; } = 1;
// public IEnumerable<ConveyorSlice> EnumerateTowardEnd() => _items;
// public IEnumerable<ConveyorSlice> EnumerateTowardStart() => _items.Reverse();
public ItemConveyor.BeltDirection GetBeltDirection() => IsReversed ? ItemConveyor.BeltDirection.TowardStart : ItemConveyor.BeltDirection.TowardEnd;
public ItemConveyor.IBeltMovement GetMovementPolicy() => MovementSystem;
public IEnumerable<IBeltPort> GetPorts() => [StartPort, EndPort, .. OtherPorts];
// public IEnumerable<IBeltSlotProfile> GetPorts() => [StartPort, EndPort,
// new ItemConveyor.ConveyorPort(){
// BeltT = new ItemConveyor.BeltTOffset(.5f),
// Face = Direction.Right,
// Direction = PortAccess.InOut,
// PullPush = TransferMode.Passive,
// MovementConveyor = this,
// LocalOffset = Position,
// },
// new ItemConveyor.ConveyorPort(){
// BeltT = new ItemConveyor.BeltTOffset(.5f),
// Face = Direction.Left,
// Direction = PortAccess.InOut,
// PullPush = TransferMode.Passive,
// MovementConveyor = this,
// LocalOffset = Position,
// },
// new ItemConveyor.ConveyorPort(){
// BeltT = new ItemConveyor.BeltTOffset(.5f),
// Face = Direction.Up,
// Direction = PortAccess.In,
// PullPush = TransferMode.Passive,
// MovementConveyor = this,
// LocalOffset = Position,
// }
// ];Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X*b.Item.Width
public void OnResolved()
{
if (Engine.IsEditorHint())
{
return;
}
var timer = new Timer() { WaitTime = .05f, Autostart = true };
AddChild(timer);
timer.Timeout += () => MovementSystem.AdvanceBelt(this, (float)timer.WaitTime);
// Items.Add(new ConveyorSlice(new TestItem(), 0));
// StartPort = new ConveyorPort(this,
// new BeltPortProfile(Position, Direction.Back, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
// (item, offset) =>
// {
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// },
// (item, offset) => { _items.Insert(0, new(item, offset)); return true;});
// EndPort = new ConveyorPort(this,
// new BeltPortProfile(Position, Direction.Front, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
// (item, offset) =>
// {
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, Length, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One);
// },
// (item, offset) => { _items.Add(new(item, offset)); return true;});
if (StartPort is null || EndPort is null)
{
throw new Exception();
}
foreach (var item in GetPorts())
{
var points = item.Points();
GridRegistry.Register(item, [.. points]);
}
StartPort.TryInsert(new TestItem(), LaneSpan.Shifted(LaneSpan.One,0), 0);
// StartPort.TryInsert(new TestItem(), LaneSpan.Sh fted(LaneSpan.One,1), 0);
// Items.Insert(new ConveyorSlice(new TestItem(), 0));
// _items.Add(new (new TestItem()));
}
public override void _Ready()
{
// _itemsBinding = _items.Bind();
// _itemsBinding.OnModify(() =>
// {
// });
// OnResolved();
base._Ready();
//test
}
//ChatGPT Assisted
public BeltObstacle GetDistanceToNextItem(
ItemConveyor.BeltDirection beltDirection,
float itemBeltT,
float maxDistToCheck,
LaneSpan laneSpan,
HashSet<IMovementConveyor>? visited = null)
{
if (beltDirection == ItemConveyor.BeltDirection.NotMoving)
{
throw new NotSupportedException();
}
visited ??= [];
if (!visited.Add(this))
{
return new BeltObstacle(0);
}
bool towardStart = beltDirection == ItemConveyor.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) - ItemConveyor.ITEMSIZE, item, laneSpan);
// return MathF.Abs(itemBeltT - item.BeltT);//TODO UseLaneSpan inadditon
}
// 2⃣ No local item → try crossing into adjacent conveyor
return GetDistanceAcrossPort(
towardStart,
itemBeltT,
maxDistToCheck,
laneSpan,
visited
);
}
//ChatGPT Assisted
private IOption<ConveyorSlice> FindNextLocalItem(
bool towardStart,
float itemBeltT,
LaneSpan laneSpan)
{
var sequence = towardStart
? EnumerateTowardStart()
: EnumerateTowardEnd();
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<IMovementConveyor> 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 ItemConveyor.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 == ItemConveyor.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 };
// return new BeltObstacle(recursiveDistance.Kind, recursiveDistance.DistanceToCenter + distanceToBoundary, recursiveDistance.LaneSpan, recursiveDistance.Conveyor, recursiveDistance.Item,null);
// return distanceToBoundary + recursiveDistance;
}
private IOption<ConveyorPort> FindFacingConveyorPort(
IBeltPort slot)
{
var targetpos = slot.Profile.LocalOffset.TransformDirection(Vector3I.Forward);
// var targetPos = port.Profile.Position + port.Profile.Face.ToVector();
return GridRegistry
.Get<IBeltPort>(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<ConveyorPort>.Of());
}
public IOption<IBeltPort> GetPortFacing(IBeltPort slot)
{
var toCheck = new List<Vector3I>();
for (int i = 0; i < slot.Profile.Width; i++)
{
toCheck.Add(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward));
}
return GridRegistry
.Get<IBeltPort>([.. toCheck])
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
.FirstOrNone();
}
private IOption<LaneSpan> MapLaneOrFail(
IBeltPort from,
IBeltPort to,
LaneSpan incoming)
{
// return None<LaneSpan>.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<LaneSpan>.Of();
}
return new LaneSpan((ushort)newStart, (ushort)newEnd).ToOption();
}
private ItemConveyor.BeltDirection DirectionAwayFromEnd(ItemConveyor.ConveyorEnd conveyorEnd) => conveyorEnd switch {ItemConveyor.ConveyorEnd.Start=>ItemConveyor.BeltDirection.TowardEnd,ItemConveyor.ConveyorEnd.End=>ItemConveyor.BeltDirection.TowardStart};
public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd() => Items.EnumerateTowardEnd();
public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart() => Items.EnumerateTowardStart();
private static Vector3[] _Square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
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, ItemConveyor.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 ItemConveyor.BeltTEnd end)
{
var startBeltT = end.End == ItemConveyor.ConveyorEnd.Start ? 0 : Length;
var obstacle = GetDistanceToNextItem(ItemConveyor.SwapEnd(end.End).DirectionTo(), startBeltT, offset, lane);
var distanceAllowed = obstacle.Distance;
return ItemConveyor.ITEMSIZE < distanceAllowed;
}
else if (beltT is ItemConveyor.BeltTOffset endOffset)
{
return HasClearance(endOffset.T,lane);
// var lowerObstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, endOffset.T, ItemConveyor.ITEMSIZE,LaneSpan.One);
// var upperObstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, endOffset.T, ItemConveyor.ITEMSIZE,LaneSpan.One);
// var lowerDistanceAllowed = lowerObstacle.DistanceToCenter - (lowerObstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// var upperObstacleAllowed = upperObstacle.DistanceToCenter - (upperObstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// return ItemConveyor.ITEMSIZE < lowerDistanceAllowed && ItemConveyor.ITEMSIZE < upperObstacleAllowed;
}
// return true;
throw new NotImplementedException();
}
// if (beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End){
// accept = (IBeltItem item, float offset) =>
// {
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// };
// }
bool tryInsert(IBeltItem item, float offset, LaneSpan laneSpan)
{
if (beltT is ItemConveyor.BeltTEnd end)
{
if (end.End == ItemConveyor.ConveyorEnd.End)
{
Items.Insert(new (item, Length-offset){LaneSpan = laneSpan});
// _items.Add(new(item, Length-offset));
}
else
{
Items.Insert(new (item, offset) {LaneSpan = laneSpan});
// _items.Insert(0, new(item, offset));
}
return true;
}
else if (beltT is ItemConveyor.BeltTOffset endOffset)
{
if (!HasClearance(endOffset.T, LaneSpan.One))
{
return false;
}
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(
ItemConveyor.BeltDirection.TowardStart,
centerT,
ItemConveyor.ITEMSIZE,
span
);
var upper = GetDistanceToNextItem(
ItemConveyor.BeltDirection.TowardEnd,
centerT,
ItemConveyor.ITEMSIZE,
span
);
float lowerAllowed =
lower.Distance;// -
// (lower.IsItem ? ItemConveyor.ITEMSIZE : 0);
float upperAllowed =
upper.Distance;// -
// (upper.IsItem ? ItemConveyor.ITEMSIZE : 0);
return ItemConveyor.ITEMSIZE < lowerAllowed &&
ItemConveyor.ITEMSIZE < upperAllowed;
}
}
// Items.Add(new ConveyorSlice(new TestItem(), 0));
// StartPort = new ConveyorPort(this,
// new BeltPortProfile(Position, Direction.Back, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
// (item, offset) =>
// {
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// },
// (item, offset) => { _items.Insert(0, new(item, offset)); return true;});
// EndPort = new ConveyorPort(this,
// new BeltPortProfile(Position, Direction.Front, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
// (item, offset) =>
// {
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, Length, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One);
// },
// (item, offset) => { _items.Add(new(item, offset)); return true;});
//TODO Should liklely account for max search distance where the conveyorm may be needed to know
public record class BeltObstacle(float Distance)
{
// public ObstacleKind Kind { get; }
// public BeltObstacle()
// {
// }
// public float DistanceToCenter { get; } // always center / reference
// public LaneSpan LaneSpan { get; } // only meaningful for Item
// public ConveyorSlice? Item { get; } // only if Kind == Item
// public IMovementConveyor? Conveyor { get; } // boundary case
// public IBeltPort? Port { get; } // Port case
// public bool IsItem => Kind == ObstacleKind.Item;
// public bool IsBoundary => Kind == ObstacleKind.Boundary;
}
// public enum ObstacleKind
// {
// None,
// Item,
// Boundary,
// }
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)
{
}

View File

@@ -0,0 +1 @@
uid://j24fuotdwwx4

View File

@@ -0,0 +1,177 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Linq;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Godot.Collections;
using SJK.Functional;
//Will Liklely be the game instead of a node like this
[Meta(typeof(IAutoNode))]// [Tool]
public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridQuery<LayeredEquipment>>, IProvide<IEquipmentComponentRegistry>, IProvide<IVoxelGridRegistry>, IProvide<IItemRenderer>
{
public override void _Notification(int what) => this.Notify(what);
private IVoxelGridQuery<LayeredEquipment> _voxelGrid = default!;
IVoxelGridQuery<LayeredEquipment> IProvide<IVoxelGridQuery<LayeredEquipment>>.Value() => _voxelGrid;
private IVoxelGridRegistry _voxelGridRegistry = default!;
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => _voxelGridRegistry;
private IItemRenderer _itemRenderer = default!;
IItemRenderer IProvide<IItemRenderer>.Value() => _itemRenderer;
public override void _Ready()
{
TestStructural.Test();
GD.Print();
base._Ready();
_voxelGrid = new EquipmentVoxelGrid();
_equipmentComponentRegistry = new EquipmentComponentRegistry();
_voxelGridRegistry = new VoxelRegistry();
_itemRenderer = new TestItemRendered();
AddChild(_itemRenderer as Node);
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST
AddChild(timer);//TEST
// timer.Timeout += _itemRenderer.Tick;//TEST
this.Provide();
}
public override void _Process(double delta)
{
base._Process(delta);
// GD.Print(_voxelGridRegistry.Get<ISlotFace>(Vector3I.Zero).First().GetConnectingFace());
}
IEquipmentComponentRegistry _equipmentComponentRegistry;
IEquipmentComponentRegistry IProvide<IEquipmentComponentRegistry>.Value() => _equipmentComponentRegistry;
}
// [Meta, Id("voxel_grid")]
public sealed partial class EquipmentVoxelGrid : IVoxelGridQuery<LayeredEquipment>
{
private System.Collections.Generic.Dictionary<Vector3I, LayeredEquipment> _data = new();
public void AddEquipment(Vector3I position, Equipment equipment)
{
if (!_data.TryGetValue(position, out var layers))
{
_data[position] = layers = new();
}
layers.Add(equipment);
}
//TODO Use Options instead of nu;lable
public LayeredEquipment GetVoxel(Vector3I position) => _data.TryGetValue(position, out var result) ? result : new();
public IEnumerable<Equipment> GetEquipmentAt(Vector3I position) => GetVoxel(position).GetEquipments();
// public
// public void SetEquipmentAt(Vector3I position, T value)
// {
// _data[position] = value;
// GD.Print(value);
// }
public LayeredEquipment SetVoxel(Vector3I position, LayeredEquipment value) => _data[position] = value;
public bool IsOccupied(Vector3I cell) => _data.ContainsKey(cell);
}
public interface IVoxelGridQuery<T>
{
T GetVoxel(Vector3I cell);
T SetVoxel(Vector3I cell, T value);
bool IsOccupied(Vector3I cell);
}
public class LayeredEquipment
{
public int Count => _equipment.Count;
private readonly HashSet<Equipment> _equipment = new();
public IEnumerable<Equipment> GetEquipments() => _equipment;
public bool Add(Equipment equipment) => _equipment.Add(equipment);
public bool Remove(Equipment equipment) => _equipment.Remove(equipment);
public void AddMany(params Equipment[] equipment)
{
foreach (var item in equipment)
{
Add(item);
}
}
public void RemoveMany(params Equipment[] equipment)
{
foreach (var item in equipment)
{
Remove(item);
}
}
public void IfAny(Action<IEnumerable<Equipment>> action)
{
if (Count > 0)
{
action(GetEquipments());
}
}
}
public sealed class SlotDescriptor {
public SlotDirection Direction { get; }
public SlotDescriptor(
SlotDirection direction
)
{
Direction = direction;
}
}
public abstract class SlotLogic<TPayload> {
public SlotDescriptor Descriptor { get; }
protected SlotLogic(SlotDescriptor descriptor) {
Descriptor = descriptor;
}
public abstract bool CanTransfer(
TPayload payload
);
public abstract bool TryTransfer(
TPayload payload
);
}
public sealed class ItemSlotLogic
: SlotLogic<IBeltItem> {
public ItemSlotLogic(SlotDescriptor descriptor)
: base(descriptor) {}
public override bool CanTransfer(IBeltItem item) => false;
// item.Count > 0;
public override bool TryTransfer(IBeltItem item) {
// routing rules
return true;
}
}
[Meta]
public partial class ItemSlotNode
: SlotComponentNode {
private ItemSlotLogic _logic;
public override void _Ready() {
base._Ready();
_logic = new ItemSlotLogic(
new SlotDescriptor(Direction)
);
}
public void OnResolved()
{
}
public void Tick(IBeltItem stack) {
if (_logic.CanTransfer(stack))
_logic.TryTransfer(stack);
}
}
public enum SlotDirection
{
Input,
Output
}

View File

@@ -0,0 +1 @@
uid://dcrb286hmpli

View File

@@ -0,0 +1,93 @@
[gd_scene format=3 uid="uid://dfacxkkkc0v10"]
[ext_resource type="Script" uid="uid://dcrb286hmpli" path="res://src/VoxelGrid/VoxelGridNode.cs" id="1_tsdpe"]
[ext_resource type="Script" uid="uid://ee5aoxi8mjnw" path="res://src/VoxelGrid/BeltPort.cs" id="6_2wkfx"]
[ext_resource type="PackedScene" uid="uid://c4h7mwnfrdesg" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn" id="6_mxaon"]
[sub_resource type="Curve3D" id="Curve3D_mxaon"]
_data = {
"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0.5, -0.5, 0, 0, 0, 0, 0, 0, 0, 0.5, 0),
"tilts": PackedFloat32Array(0, 0)
}
point_count = 2
[node name="VoxelGridNode" type="Node3D" unique_id=825696340]
script = ExtResource("1_tsdpe")
[node name="Camera3D" type="Camera3D" parent="." unique_id=75458542]
transform = Transform3D(0.6279494, -0.32487354, 0.70720345, -8.896721e-09, 0.908705, 0.4174389, -0.77825415, -0.26213053, 0.5706208, 4.635174, 2.6038146, 2.739409)
[node name="ConveyorBeltStraight5" parent="." unique_id=975225936 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 0)
Width = 1
[node name="ConveyorBeltStraight22" parent="." unique_id=81137442 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 2, 0, 0)
Width = 1
[node name="ConveyorBeltStraight13" parent="." unique_id=607296315 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 0, 0, 0)
Width = 1
[node name="ConveyorBeltStraight26" parent="." unique_id=626657643 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 0)
Width = 1
[node name="ConveyorBeltStraight6" parent="." unique_id=264656404 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -1, 0, 0)
Width = 1
[node name="ConveyorBeltStraight23" parent="." unique_id=1438072389 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -2, 0, 0)
Width = 1
[node name="ConveyorBeltStraight14" parent="." unique_id=1045608025 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -4, 0, 0)
Width = 1
[node name="ConveyorBeltStraight27" parent="." unique_id=899289297 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -3, 0, 0)
Width = 1
[node name="ConveyorBeltStraight7" parent="." unique_id=1602707514 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 1)
Width = 1
[node name="ConveyorBeltStraight24" parent="." unique_id=904248251 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 2, 0, 1)
Width = 1
[node name="ConveyorBeltStraight15" parent="." unique_id=533235542 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 0, 0, 1)
Width = 1
[node name="ConveyorBeltStraight28" parent="." unique_id=2116943332 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 1)
Width = 1
[node name="ConveyorBeltStraight8" parent="." unique_id=1109978762 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -1, 0, 1)
Width = 1
[node name="ConveyorBeltStraight25" parent="." unique_id=1110001269 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -2, 0, 1)
Width = 1
[node name="ConveyorBeltStraight16" parent="." unique_id=1178816500 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -4, 0, 1)
Width = 1
[node name="ConveyorBeltStraight29" parent="." unique_id=1537356869 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -3, 0, 1)
Width = 1
[node name="Node3D2" type="Node3D" parent="." unique_id=1815100025 node_paths=PackedStringArray("Path")]
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4, 0, 1)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 3
Path = NodePath("Path3D")
[node name="Path3D" type="Path3D" parent="Node3D2" unique_id=1525648764]
curve = SubResource("Curve3D_mxaon")