added forge addon
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Test (push) Successful in 6m56s
Create tag and build when new code gets to main / Export (push) Successful in 9m3s

This commit is contained in:
2026-02-08 15:16:01 +01:00
parent 2b74c9e70c
commit c4be97e0de
163 changed files with 6975 additions and 141 deletions

View File

@@ -0,0 +1,81 @@
// Copyright © Gamesmiths Guild.
using System.Diagnostics;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://cnjrjkgwyewjx")]
public partial class EffectArea2D : Area2D
{
private EffectApplier? _effectApplier;
[Export]
public Node? EffectOwner { get; set; }
[Export]
public Node? EffectSource { get; set; }
[Export]
public int EffectLevel { get; set; } = 1;
[Export]
public EffectTriggerMode TriggerMode { get; set; }
private IForgeEntity? OwnerEntity => EffectOwner as IForgeEntity;
private IForgeEntity? SourceEntity => EffectSource as IForgeEntity;
public override void _Ready()
{
if (EffectOwner is not null && EffectOwner is not IForgeEntity)
{
GD.PushError($"{nameof(EffectOwner)} must implement {nameof(IForgeEntity)}.");
}
base._Ready();
_effectApplier = new EffectApplier(this);
switch (TriggerMode)
{
case EffectTriggerMode.OnEnter:
BodyEntered += ApplyEffects;
AreaEntered += ApplyEffects;
break;
case EffectTriggerMode.OnExit:
BodyExited += ApplyEffects;
AreaExited += ApplyEffects;
break;
case EffectTriggerMode.OnStay:
BodyEntered += AddEffects;
AreaEntered += AddEffects;
BodyExited += RemoveEffects;
AreaExited += RemoveEffects;
break;
}
}
private void ApplyEffects(Node2D node)
{
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
_effectApplier.ApplyEffects(node, OwnerEntity, SourceEntity, EffectLevel);
}
private void AddEffects(Node2D node)
{
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
_effectApplier.AddEffects(node, OwnerEntity, SourceEntity, EffectLevel);
}
private void RemoveEffects(Node2D node)
{
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
_effectApplier.RemoveEffects(node);
}
}

View File

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

View File

@@ -0,0 +1,81 @@
// Copyright © Gamesmiths Guild.
using System.Diagnostics;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://tbuwwf03ikr3")]
public partial class EffectArea3D : Area3D
{
private EffectApplier? _effectApplier;
[Export]
public Node? EffectOwner { get; set; }
[Export]
public Node? EffectSource { get; set; }
[Export]
public int EffectLevel { get; set; } = 1;
[Export]
public EffectTriggerMode TriggerMode { get; set; }
private IForgeEntity? OwnerEntity => EffectOwner as IForgeEntity;
private IForgeEntity? SourceEntity => EffectSource as IForgeEntity;
public override void _Ready()
{
if (EffectOwner is not null && EffectOwner is not IForgeEntity)
{
GD.PushError($"{nameof(EffectOwner)} must implement {nameof(IForgeEntity)}.");
}
base._Ready();
_effectApplier = new EffectApplier(this);
switch (TriggerMode)
{
case EffectTriggerMode.OnEnter:
BodyEntered += ApplyEffects;
AreaEntered += ApplyEffects;
break;
case EffectTriggerMode.OnExit:
BodyExited += ApplyEffects;
AreaExited += ApplyEffects;
break;
case EffectTriggerMode.OnStay:
BodyEntered += AddEffects;
AreaEntered += AddEffects;
BodyExited += RemoveEffects;
AreaExited += RemoveEffects;
break;
}
}
private void ApplyEffects(Node3D node)
{
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
_effectApplier.ApplyEffects(node, OwnerEntity, SourceEntity, EffectLevel);
}
private void AddEffects(Node3D node)
{
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
_effectApplier.AddEffects(node, OwnerEntity, SourceEntity, EffectLevel);
}
private void RemoveEffects(Node3D node)
{
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
_effectApplier.RemoveEffects(node);
}
}

View File

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

View File

@@ -0,0 +1,84 @@
// Copyright © Gamesmiths Guild.
using System.Diagnostics;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://d3wjawuu1ej3s")]
public partial class EffectRayCast2D : RayCast2D
{
private EffectApplier? _effectApplier;
private GodotObject? _lastFrameCollider;
[Export]
public Node? EffectOwner { get; set; }
[Export]
public Node? EffectSource { get; set; }
[Export]
public int EffectLevel { get; set; } = 1;
[Export]
public EffectTriggerMode TriggerMode { get; set; }
private IForgeEntity? OwnerEntity => EffectOwner as IForgeEntity;
private IForgeEntity? SourceEntity => EffectSource as IForgeEntity;
public override void _Ready()
{
if (EffectOwner is not null && EffectOwner is not IForgeEntity)
{
GD.PushError($"{nameof(EffectOwner)} must implement {nameof(IForgeEntity)}.");
}
base._Ready();
_effectApplier = new EffectApplier(this);
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
GodotObject current = GetCollider();
var hasCurrent = current is Node;
var hadLast = _lastFrameCollider is Node;
// Enter: is colliding now, wasn't colliding before.
if (current is Node currentNode && !hadLast)
{
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.AddEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
else if (TriggerMode == EffectTriggerMode.OnEnter)
{
_effectApplier.ApplyEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
// Exit: Was colliding before, isn't colliding now.
if (!hasCurrent && _lastFrameCollider is Node lastNode)
{
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.RemoveEffects(lastNode);
}
else if (TriggerMode == EffectTriggerMode.OnExit)
{
_effectApplier.ApplyEffects(lastNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
_lastFrameCollider = hasCurrent ? current : null;
}
}

View File

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

View File

@@ -0,0 +1,84 @@
// Copyright © Gamesmiths Guild.
using System.Diagnostics;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://dskf1b0eecaxl")]
public partial class EffectRayCast3D : RayCast3D
{
private EffectApplier? _effectApplier;
private GodotObject? _lastFrameCollider;
[Export]
public Node? EffectOwner { get; set; }
[Export]
public Node? EffectSource { get; set; }
[Export]
public int EffectLevel { get; set; } = 1;
[Export]
public EffectTriggerMode TriggerMode { get; set; }
private IForgeEntity? OwnerEntity => EffectOwner as IForgeEntity;
private IForgeEntity? SourceEntity => EffectSource as IForgeEntity;
public override void _Ready()
{
if (EffectOwner is not null && EffectOwner is not IForgeEntity)
{
GD.PushError($"{nameof(EffectOwner)} must implement {nameof(IForgeEntity)}.");
}
base._Ready();
_effectApplier = new EffectApplier(this);
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
GodotObject current = GetCollider();
var hasCurrent = current is Node;
var hadLast = _lastFrameCollider is Node;
// Enter: is colliding now, wasn't colliding before.
if (current is Node currentNode && !hadLast)
{
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.AddEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
else if (TriggerMode == EffectTriggerMode.OnEnter)
{
_effectApplier.ApplyEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
// Exit: Was colliding before, isn't colliding now.
if (!hasCurrent && _lastFrameCollider is Node lastNode)
{
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.RemoveEffects(lastNode);
}
else if (TriggerMode == EffectTriggerMode.OnExit)
{
_effectApplier.ApplyEffects(lastNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
_lastFrameCollider = hasCurrent ? current : null;
}
}

View File

@@ -0,0 +1 @@
uid://7bhjrbrsll5y

View File

@@ -0,0 +1,99 @@
// Copyright © Gamesmiths Guild.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://dni75h8pnigh2")]
public partial class EffectShapeCast2D : ShapeCast2D
{
private readonly HashSet<GodotObject> _lastFrameColliders = [];
private EffectApplier? _effectApplier;
[Export]
public Node? EffectOwner { get; set; }
[Export]
public Node? EffectSource { get; set; }
[Export]
public int EffectLevel { get; set; } = 1;
[Export]
public EffectTriggerMode TriggerMode { get; set; }
private IForgeEntity? OwnerEntity => EffectOwner as IForgeEntity;
private IForgeEntity? SourceEntity => EffectSource as IForgeEntity;
public override void _Ready()
{
if (EffectOwner is not null && EffectOwner is not IForgeEntity)
{
GD.PushError($"{nameof(EffectOwner)} must implement {nameof(IForgeEntity)}.");
}
base._Ready();
_effectApplier = new EffectApplier(this);
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
var collisions = GetCollisionCount();
var collidersThisFrame = new List<GodotObject>();
for (var i = 0; i < collisions; i++)
{
GodotObject current = GetCollider(i);
var hadLast = _lastFrameColliders.Contains(current);
_lastFrameColliders.Add(current);
collidersThisFrame.Add(current);
// Enter: is colliding now, wasn't colliding before.
if (current is Node currentNode && !hadLast)
{
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.AddEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
else if (TriggerMode == EffectTriggerMode.OnEnter)
{
_effectApplier.ApplyEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
}
// Exit: Was colliding before, isn't colliding now.
foreach (GodotObject? lastCollider in _lastFrameColliders.Except(collidersThisFrame))
{
_lastFrameColliders.Remove(lastCollider);
if (lastCollider is not Node lastNode)
{
continue;
}
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.RemoveEffects(lastNode);
}
else if (TriggerMode == EffectTriggerMode.OnExit)
{
_effectApplier.ApplyEffects(lastNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
}
}

View File

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

View File

@@ -0,0 +1,99 @@
// Copyright © Gamesmiths Guild.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://e2ynjdppura3")]
public partial class EffectShapeCast3D : ShapeCast3D
{
private readonly HashSet<GodotObject> _lastFrameColliders = [];
private EffectApplier? _effectApplier;
[Export]
public Node? EffectOwner { get; set; }
[Export]
public Node? EffectSource { get; set; }
[Export]
public int EffectLevel { get; set; } = 1;
[Export]
public EffectTriggerMode TriggerMode { get; set; }
private IForgeEntity? OwnerEntity => EffectOwner as IForgeEntity;
private IForgeEntity? SourceEntity => EffectSource as IForgeEntity;
public override void _Ready()
{
if (EffectOwner is not null && EffectOwner is not IForgeEntity)
{
GD.PushError($"{nameof(EffectOwner)} must implement {nameof(IForgeEntity)}.");
}
base._Ready();
_effectApplier = new EffectApplier(this);
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
Debug.Assert(_effectApplier is not null, $"{_effectApplier} should have been initialized on _Ready().");
var collisions = GetCollisionCount();
var collidersThisFrame = new List<GodotObject>();
for (var i = 0; i < collisions; i++)
{
GodotObject current = GetCollider(i);
var hadLast = _lastFrameColliders.Contains(current);
_lastFrameColliders.Add(current);
collidersThisFrame.Add(current);
// Enter: is colliding now, wasn't colliding before.
if (current is Node currentNode && !hadLast)
{
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.AddEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
else if (TriggerMode == EffectTriggerMode.OnEnter)
{
_effectApplier.ApplyEffects(currentNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
}
// Exit: Was colliding before, isn't colliding now.
foreach (GodotObject? lastCollider in _lastFrameColliders.Except(collidersThisFrame))
{
_lastFrameColliders.Remove(lastCollider);
if (lastCollider is not Node lastNode)
{
continue;
}
if (TriggerMode == EffectTriggerMode.OnStay)
{
_effectApplier.RemoveEffects(lastNode);
}
else if (TriggerMode == EffectTriggerMode.OnExit)
{
_effectApplier.ApplyEffects(lastNode, OwnerEntity, SourceEntity, EffectLevel);
}
}
}
}

View File

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

View File

@@ -0,0 +1,21 @@
// Copyright © Gamesmiths Guild.
namespace Gamesmiths.Forge.Godot.Nodes;
public enum EffectTriggerMode
{
/// <summary>
/// Add effects when entering the area.
/// </summary>
OnEnter = 0,
/// <summary>
/// Add effects when exiting the area.
/// </summary>
OnExit = 1,
/// <summary>
/// Add effects when entering the area and removes when exiting it.
/// </summary>
OnStay = 2,
}

View File

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

View File

@@ -0,0 +1,86 @@
// Copyright © Gamesmiths Guild.
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Gamesmiths.Forge.Attributes;
using Gamesmiths.Forge.Godot.Editor.Attributes;
using Godot;
using Godot.Collections;
namespace Gamesmiths.Forge.Godot.Nodes;
[Tool]
[GlobalClass]
[Icon("uid://dnqaqpc02lx3p")]
public partial class ForgeAttributeSet : Node
{
[Export]
public string AttributeSetClass { get; set; } = string.Empty;
[Export]
public Dictionary<string, AttributeValues>? InitialAttributeValues { get; set; }
public override void _Ready()
{
base._Ready();
InitialAttributeValues ??= [];
}
public AttributeSet? GetAttributeSet()
{
if (string.IsNullOrEmpty(AttributeSetClass))
{
return null;
}
Debug.Assert(
InitialAttributeValues is not null,
$"{nameof(InitialAttributeValues)} should have been initialized on _Ready.");
Type? type = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.Name == AttributeSetClass);
if (type is null || !typeof(AttributeSet).IsAssignableFrom(type))
{
return null;
}
var instance = (AttributeSet?)Activator.CreateInstance(type);
if (instance is null)
{
return null;
}
foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (prop.PropertyType == typeof(EntityAttribute))
{
var name = prop.Name;
if (InitialAttributeValues.TryGetValue(name, out AttributeValues? value))
{
SetAttributeValue("SetAttributeBaseValue", instance, prop, value.Default);
SetAttributeValue("SetAttributeMinValue", instance, prop, value.Min);
SetAttributeValue("SetAttributeMaxValue", instance, prop, value.Max);
}
}
}
return instance;
}
private static void SetAttributeValue(string methodName, AttributeSet instance, PropertyInfo prop, int value)
{
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
// Do not attempt this in production environments without adult supervision.
MethodInfo? method = typeof(AttributeSet).GetMethod(
methodName,
BindingFlags.Static | BindingFlags.NonPublic);
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
method?.Invoke(null, [(EntityAttribute?)prop.GetValue(instance), value]);
}
}

View File

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

View File

@@ -0,0 +1,121 @@
// Copyright © Gamesmiths Guild.
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Godot.Core;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://snulmvxydrp4")]
public abstract partial class ForgeCueHandler : Node, ICueHandler
{
[Export]
public string? CueTag { get; set; }
public override void _Ready()
{
if (Engine.IsEditorHint())
{
return;
}
base._Ready();
if (string.IsNullOrEmpty(CueTag))
{
return;
}
ForgeManagers.Instance.CuesManager.RegisterCue(
Tag.RequestTag(ForgeManagers.Instance.TagsManager, CueTag), this);
}
public override void _ExitTree()
{
base._ExitTree();
if (string.IsNullOrEmpty(CueTag))
{
return;
}
ForgeManagers.Instance.CuesManager.UnregisterCue(
Tag.RequestTag(ForgeManagers.Instance.TagsManager, CueTag), this);
}
#pragma warning disable CA1707, IDE1006, SA1300 // Identifiers should not contain underscores
public void OnApply(IForgeEntity? target, CueParameters? parameters)
{
if (target is IForgeEntity forgeEntity)
{
_CueOnApply(forgeEntity, parameters);
}
_CueOnApply(parameters);
}
public virtual void _CueOnApply(IForgeEntity forgeEntity, CueParameters? parameters)
{
}
public virtual void _CueOnApply(CueParameters? parameters)
{
}
public void OnExecute(IForgeEntity? target, CueParameters? parameters)
{
if (target is IForgeEntity forgeEntity)
{
_CueOnExecute(forgeEntity, parameters);
}
_CueOnExecute(parameters);
}
public virtual void _CueOnExecute(IForgeEntity forgeEntity, CueParameters? parameters)
{
}
public virtual void _CueOnExecute(CueParameters? parameters)
{
}
public void OnRemove(IForgeEntity? target, bool interrupted)
{
if (target is IForgeEntity forgeEntity)
{
_CueOnRemove(forgeEntity, interrupted);
}
_CueOnRemove(interrupted);
}
public virtual void _CueOnRemove(IForgeEntity forgeEntity, bool interrupted)
{
}
public virtual void _CueOnRemove(bool interrupted)
{
}
public void OnUpdate(IForgeEntity? target, CueParameters? parameters)
{
if (target is IForgeEntity forgeEntity)
{
_CueOnUpdate(forgeEntity, parameters);
}
_CueOnUpdate(parameters);
}
public virtual void _CueOnUpdate(IForgeEntity forgeEntity, CueParameters? parameters)
{
}
public virtual void _CueOnUpdate(CueParameters? parameters)
{
}
}

View File

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

View File

@@ -0,0 +1,14 @@
// Copyright © Gamesmiths Guild.
using Gamesmiths.Forge.Godot.Resources;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://bpl454nqdpfjx")]
public partial class ForgeEffect : Node
{
[Export]
public ForgeEffectData? EffectData { get; set; }
}

View File

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

View File

@@ -0,0 +1,67 @@
// Copyright © Gamesmiths Guild.
using System.Collections.Generic;
using Gamesmiths.Forge.Attributes;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Events;
using Gamesmiths.Forge.Godot.Core;
using Gamesmiths.Forge.Godot.Resources;
using Godot;
namespace Gamesmiths.Forge.Godot.Nodes;
[GlobalClass]
[Icon("uid://cu6ncpuumjo20")]
public partial class ForgeEntity : Node, IForgeEntity
{
[Export]
public ForgeTagContainer BaseTags { get; set; } = new();
public EntityAttributes Attributes { get; set; } = null!;
public EntityTags Tags { get; set; } = null!;
public EffectsManager EffectsManager { get; set; } = null!;
public EntityAbilities Abilities { get; set; } = null!;
public EventManager Events { get; set; } = null!;
public override void _Ready()
{
base._Ready();
Tags = new(BaseTags.GetTagContainer());
EffectsManager = new EffectsManager(this, ForgeManagers.Instance.CuesManager);
Abilities = new EntityAbilities(this);
Events = new EventManager();
List<AttributeSet> attributeSetList = [];
foreach (Node node in GetChildren())
{
if (node is ForgeAttributeSet attributeSetNode)
{
AttributeSet? attributeSet = attributeSetNode.GetAttributeSet();
if (attributeSet is not null)
{
attributeSetList.Add(attributeSet);
}
}
}
Attributes = new EntityAttributes([.. attributeSetList]);
var effectApplier = new EffectApplier(this);
effectApplier.ApplyEffects(this, this, this);
}
public override void _Process(double delta)
{
base._Process(delta);
EffectsManager.UpdateEffects(delta);
}
}

View File

@@ -0,0 +1 @@
uid://8uj04dfe8oql