// Copyright © Gamesmiths Guild. using System; using System.Linq; using Gamesmiths.Forge.Abilities; using Gamesmiths.Forge.Godot.Core; using Gamesmiths.Forge.Godot.Resources.Statescript; using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers; using Gamesmiths.Forge.Statescript; using Godot; namespace Gamesmiths.Forge.Godot.Resources.Abilities; /// /// A implementation that creates a from a /// serialized resource. The graph is built once and cached, then shared across all /// ability instances using the Flyweight pattern. Each creates its own /// with independent state. /// /// /// If any node in the graph uses an , the behavior automatically detects /// the associated implementation and produces a /// with a data binder that maps activation data fields into graph variables. /// When no activation data resolver is present, a plain (without data /// support) is created. /// [Tool] [GlobalClass] [Icon("uid://b6yrjb46fluw3")] public partial class StatescriptAbilityBehavior : ForgeAbilityBehavior { private Graph? _cachedGraph; private IActivationDataProvider? _cachedProvider; private bool _providerResolved; /// /// Gets or sets the Statescript graph resource that defines the ability's behavior. /// [Export] public StatescriptGraph? Statescript { get; set; } /// public override IAbilityBehavior GetBehavior() { if (Statescript is null) { GD.PushError("StatescriptAbilityBehavior: Statescript is null."); throw new InvalidOperationException("StatescriptAbilityBehavior requires a valid Statescript assigned."); } _cachedGraph ??= StatescriptGraphBuilder.Build(Statescript); if (!_providerResolved) { _cachedProvider = FindActivationDataProvider(Statescript); _providerResolved = true; } if (_cachedProvider is not null) { return _cachedProvider.CreateBehavior(_cachedGraph); } return new GraphAbilityBehavior(_cachedGraph); } private static IActivationDataProvider? FindActivationDataProvider(StatescriptGraph graph) { foreach (StatescriptNode node in graph.Nodes) { foreach (StatescriptNodeProperty binding in node.PropertyBindings) { if (binding.Resolver is ActivationDataResolverResource { ProviderClassName.Length: > 0 } resolver) { return InstantiateProvider(resolver.ProviderClassName); } } } return null; } private static IActivationDataProvider? InstantiateProvider(string className) { Type? type = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .FirstOrDefault( x => typeof(IActivationDataProvider).IsAssignableFrom(x) && !x.IsAbstract && !x.IsInterface && x.Name == className); if (type is null) { return null; } return Activator.CreateInstance(type) as IActivationDataProvider; } }