Compare commits

...

15 Commits

Author SHA1 Message Date
7bf19868e7 Setup the base for abilities and events
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Successful in 5m6s
2026-03-22 16:28:57 +01:00
d1f83525b1 updating mana through cues 2026-03-18 16:59:52 +01:00
4bcbda9690 fix: inputs were eaten by a tutorial text because of node ordering
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Successful in 5m12s
Create tag and build when new code gets to main / ReleaseName (push) Successful in 3s
Create tag and build when new code gets to main / Release (push) Successful in 13m52s
2026-03-18 11:10:06 +01:00
e51ef5a517 probably fixed stuttering of the camera and weapon animations 2026-03-18 11:02:08 +01:00
50de6abb5d mana bar 2026-03-15 21:26:59 +01:00
95616f61fc Implemented mana regeneration
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 40s
Create tag and build when new code gets to main / Export (push) Successful in 5m18s
2026-03-11 16:29:09 +01:00
b15a4fef95 empowered action as a forge ability 2026-03-11 15:56:17 +01:00
14d29d68bb Setup empowered action as a Forge ability 2026-03-10 09:22:39 +01:00
9d612682ec created a normal process function
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 41s
Create tag and build when new code gets to main / Export (push) Successful in 4m39s
2026-03-08 18:12:57 +01:00
9bfe37af62 trying cache
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 30s
Create tag and build when new code gets to main / Export (push) Successful in 4m56s
2026-03-08 10:08:06 +01:00
55eba7fcc8 trying to remove xunit
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 27s
Create tag and build when new code gets to main / Export (push) Failing after 3m37s
2026-03-08 09:49:18 +01:00
7a3e61b86f added lights in dark tutorial area 2026-03-08 09:46:33 +01:00
8153ec07e7 Revert "removing tests because they might break the solution"
This reverts commit 3a21f00528.
2026-03-08 09:44:02 +01:00
ddc85655be Revert "removed internal"
This reverts commit 5408f455af.
2026-03-08 09:43:38 +01:00
c92eb19a1c Revert "removed null!"
This reverts commit 290f79afd4.
2026-03-08 09:43:12 +01:00
76 changed files with 1861 additions and 372 deletions

View File

@@ -83,6 +83,8 @@ jobs:
Export:
runs-on: godot
env:
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
needs:
- BumpTag

View File

@@ -125,25 +125,14 @@
</ItemGroup>
<ItemGroup>
<Folder Include="addons\" />
<Folder Include="tests\" />
<Folder Include="tools\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RustyOptions" Version="0.10.1" />
</ItemGroup>
<Import Project="addons/forge/Forge.props" />
<!-- XUnit -->
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2" />
</ItemGroup>
<!-- gdUnit4 package dependencies -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
@@ -154,5 +143,4 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeEditing/SuppressNullableWarningFix/Enabled/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>

View File

@@ -8,7 +8,7 @@ namespace Gamesmiths.Forge.Godot.Core;
public class ForgeManagers
{
public static ForgeManagers Instance { get; private set; }
public static ForgeManagers Instance { get; private set; } = null!;
public TagsManager TagsManager { get; private set; }

View File

@@ -11,7 +11,7 @@ public partial class AttributeEditorProperty : EditorProperty
private const int ButtonSize = 26;
private const int PopupSize = 300;
private Label _label;
private Label _label = null!;
public override void _Ready()
{

View File

@@ -14,7 +14,7 @@ namespace Gamesmiths.Forge.Godot.Editor.Attributes;
[Tool]
public partial class AttributeSetClassEditorProperty : EditorProperty
{
private OptionButton _optionButton;
private OptionButton _optionButton = null!;
public override void _Ready()
{

View File

@@ -14,7 +14,7 @@ public partial class CueKeyEditorProperty : EditorProperty
private const int ButtonSize = 26;
private const int PopupSize = 300;
private Label _label;
private Label _label = null!;
public override void _Ready()
{

View File

@@ -15,13 +15,13 @@ public partial class TagContainerEditorProperty : EditorProperty
{
private readonly Dictionary<TreeItem, TagNode> _treeItemToNode = [];
private VBoxContainer _root;
private Button _containerButton;
private ScrollContainer _scroll;
private Tree _tree;
private VBoxContainer _root = null!;
private Button _containerButton = null!;
private ScrollContainer _scroll = null!;
private Tree _tree = null!;
private Texture2D _checkedIcon;
private Texture2D _uncheckedIcon;
private Texture2D _checkedIcon = null!;
private Texture2D _uncheckedIcon = null!;
private GodotStringArray _currentValue = [];

View File

@@ -13,13 +13,13 @@ public partial class TagEditorProperty : EditorProperty
{
private readonly Dictionary<TreeItem, TagNode> _treeItemToNode = [];
private VBoxContainer _root;
private Button _containerButton;
private ScrollContainer _scroll;
private Tree _tree;
private VBoxContainer _root = null!;
private Button _containerButton = null!;
private ScrollContainer _scroll = null!;
private Tree _tree = null!;
private Texture2D _checkedIcon;
private Texture2D _uncheckedIcon;
private Texture2D _checkedIcon = null!;
private Texture2D _uncheckedIcon = null!;
private string _currentValue = string.Empty;

View File

@@ -16,7 +16,7 @@ public partial class TagsEditor : VBoxContainer, ISerializationListener
{
private readonly Dictionary<TreeItem, TagNode> _treeItemToNode = [];
private TagsManager _tagsManager;
private TagsManager _tagsManager = null!;
private ForgeData? _forgePluginData;

View File

@@ -18,15 +18,15 @@ public partial class ForgeEntity : Node, IForgeEntity
[Export]
public ForgeTagContainer BaseTags { get; set; } = new();
public EntityAttributes Attributes { get; set; }
public EntityAttributes Attributes { get; set; } = null!;
public EntityTags Tags { get; set; }
public EntityTags Tags { get; set; } = null!;
public EffectsManager EffectsManager { get; set; }
public EffectsManager EffectsManager { get; set; } = null!;
public EntityAbilities Abilities { get; set; }
public EntityAbilities Abilities { get; set; } = null!;
public EventManager Events { get; set; }
public EventManager Events { get; set; } = null!;
public override void _Ready()
{

View File

@@ -46,7 +46,7 @@ kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
rm -rf \"{temp_dir}\""
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false
dotnet/embed_build_outputs=true
texture_format/bptc=true
texture_format/s3tc=true
texture_format/etc=false
@@ -125,7 +125,7 @@ Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorActi
Remove-Item -Recurse -Force '{temp_dir}'"
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false
dotnet/embed_build_outputs=true
texture_format/bptc=true
texture_format/s3tc=true
texture_format/etc=false
@@ -472,4 +472,4 @@ Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorActi
Remove-Item -Recurse -Force '{temp_dir}'"
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false
dotnet/embed_build_outputs=true

51
forge/ForgeManager.cs Normal file
View File

@@ -0,0 +1,51 @@
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Movementtests.tools;
public partial class ForgeManager : Node
{
public CuesManager CuesManager { get; private set; } = new CuesManager();
public TagsManager TagsManager { get; private set; } = new TagsManager(
[
// entities
"character.player",
"weapon",
// Statuses
"status.stunned",
// Abilities
"abilities.weapon.land",
// Events
"events.combat.damage",
"events.combat.hit",
"events.weapon.land",
// Cooldowns
"cooldown.empoweredAction",
"cooldown.empoweredSwordThrow",
// Cues
"cues.resources.mana",
]);
public static ForgeManager GetForgeManager(Node node)
{
return node.GetTree().Root.GetNode<ForgeManager>("ForgeManager");
}
public static TagsManager GetTagsManager(Node node)
{
return GetForgeManager(node).TagsManager;
}
public static CuesManager GetCuesManager(Node node)
{
return GetForgeManager(node).CuesManager;
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Gamesmiths.Forge.Abilities;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Effects.Components;
using Gamesmiths.Forge.Effects.Duration;
using Gamesmiths.Forge.Effects.Magnitudes;
using Gamesmiths.Forge.Effects.Modifiers;
using Gamesmiths.Forge.Tags;
using Godot;
using Movementtests.interfaces;
namespace Movementtests.forge.abilities;
[GlobalClass]
public partial class RAbilityBase(float cost, float cooldown) : Resource, IAbilityBase
{
[Export(PropertyHint.Range, "0,100,1,or_greater")]
public float Cost { get; set; } = cost;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float Cooldown { get; set; } = cooldown;
public RAbilityBase() : this(20.0f, 0.0f)
{
}
public virtual AbilityData Ability(TagsManager tagsManager, Node3D owner)
{
throw new NotImplementedException();
}
public virtual EffectData CostEffect(TagsManager tagsManager)
{
throw new NotImplementedException();
}
public virtual EffectData CooldownEffect(TagsManager tagsManager)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1 @@
uid://4eosgwb3h528

View File

@@ -0,0 +1,99 @@
using Gamesmiths.Forge.Abilities;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Effects.Components;
using Gamesmiths.Forge.Effects.Duration;
using Gamesmiths.Forge.Effects.Magnitudes;
using Gamesmiths.Forge.Effects.Modifiers;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Movementtests.forge.abilities;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/white/icon_animation.png")]
public partial class REmpoweredAction(float cost, float cooldown, float manaRegenPause) : Resource
{
[Export(PropertyHint.Range, "0,100,1,or_greater")]
public float Cost { get; set; } = cost;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float Cooldown { get; set; } = cooldown;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float ManaRegenPause { get; set; } = manaRegenPause;
public REmpoweredAction() : this(20.0f, 1.0f, 3.0f)
{
}
public AbilityData Ability(TagsManager tagsManager)
{
return new AbilityData(
name: "Empowered Action",
costEffect: CostEffect(tagsManager),
cooldownEffects: [CooldownEffect(tagsManager)],
instancingPolicy: AbilityInstancingPolicy.PerEntity,
behaviorFactory: () => new EmpoweredActionBehavior());
}
public EffectData CostEffect(TagsManager tagsManager)
{
return new(
"Empowered Action Mana Cost",
new DurationData(DurationType.Instant),
new[]
{
new Modifier(
"PlayerAttributeSet.Mana",
ModifierOperation.FlatBonus,
new ModifierMagnitude(
MagnitudeCalculationType.ScalableFloat,
new ScalableFloat(-Cost)
)
)
},
cues: new []
{
new CueData(
CueTags: Tag.RequestTag(tagsManager, "cues.resources.mana").GetSingleTagContainer(),
MinValue: 0,
MaxValue: 100,
MagnitudeType: CueMagnitudeType.AttributeValueChange,
MagnitudeAttribute: "PlayerAttributeSet.Mana"
)
});
}
public EffectData CooldownEffect(TagsManager tagsManager)
{
return new(
"Empowered Action Cooldown",
new DurationData(
DurationType.HasDuration,
new ModifierMagnitude(
MagnitudeCalculationType.ScalableFloat,
new ScalableFloat(Cooldown))),
effectComponents: new[]
{
new ModifierTagsEffectComponent(
tagsManager.RequestTagContainer(new[] { "cooldown.empoweredAction" })
)
});
}
}
public class EmpoweredActionBehavior : IAbilityBehavior
{
public void OnStarted(AbilityBehaviorContext context)
{
// Apply costs and cooldowns
context.AbilityHandle.CommitAbility();
context.InstanceHandle.End();
}
public void OnEnded(AbilityBehaviorContext context)
{
// Do any necessary cleanups
}
}

View File

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

View File

@@ -0,0 +1,104 @@
using Gamesmiths.Forge.Abilities;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Effects.Components;
using Gamesmiths.Forge.Effects.Duration;
using Gamesmiths.Forge.Effects.Magnitudes;
using Gamesmiths.Forge.Effects.Modifiers;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Movementtests.forge.abilities;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/white/icon_projectile.png")]
public partial class RExplodingSwordThrow(PackedScene? explosion, float cost, float cooldown) : RAbilityBase(cost, cooldown)
{
[Export] public PackedScene? Explosion { get; set; } = explosion;
public RExplodingSwordThrow() : this(null, 20.0f, 0.0f)
{
}
public override AbilityData Ability(TagsManager tagsManager, Node3D owner)
{
return new AbilityData(
name: "Exploding Sword Throw",
costEffect: CostEffect(tagsManager),
cooldownEffects: [CooldownEffect(tagsManager)],
abilityTags: Tag.RequestTag(tagsManager, "abilities.weapon.land").GetSingleTagContainer(),
instancingPolicy: AbilityInstancingPolicy.PerEntity,
behaviorFactory: () => new ExplodingSwordThrowBehavior(owner, Explosion));
}
public override EffectData CostEffect(TagsManager tagsManager)
{
return new(
"Exploding Sword Throw Mana Cost",
new DurationData(DurationType.Instant),
new[]
{
new Modifier(
"PlayerAttributeSet.Mana",
ModifierOperation.FlatBonus,
new ModifierMagnitude(
MagnitudeCalculationType.ScalableFloat,
new ScalableFloat(-Cost)
)
)
},
cues: new []
{
new CueData(
CueTags: Tag.RequestTag(tagsManager, "cues.resources.mana").GetSingleTagContainer(),
MinValue: 0,
MaxValue: 100,
MagnitudeType: CueMagnitudeType.AttributeValueChange,
MagnitudeAttribute: "PlayerAttributeSet.Mana"
)
});
}
public override EffectData CooldownEffect(TagsManager tagsManager)
{
return new(
"Exploding Sword Throw Cooldown",
new DurationData(
DurationType.HasDuration,
new ModifierMagnitude(
MagnitudeCalculationType.ScalableFloat,
new ScalableFloat(Cooldown))),
effectComponents: new[]
{
new ModifierTagsEffectComponent(
tagsManager.RequestTagContainer(new[] { "cooldown.empoweredSwordThrow" })
)
});
}
}
public class ExplodingSwordThrowBehavior(Node3D owner, PackedScene? explosion) : IAbilityBehavior
{
private Node3D _owner = owner;
private PackedScene? _explosion = explosion;
public void OnStarted(AbilityBehaviorContext context)
{
if (_explosion?.Instantiate() is not Explosion explosion)
{
context.InstanceHandle.End();
return;
}
explosion.Radius = 10f;
_owner.GetTree().GetRoot().AddChild(explosion);
explosion.GlobalPosition = _owner.GlobalPosition;
context.AbilityHandle.CommitAbility();
context.InstanceHandle.End();
}
public void OnEnded(AbilityBehaviorContext context)
{
}
}

View File

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

View File

@@ -0,0 +1,58 @@
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Effects.Duration;
using Gamesmiths.Forge.Effects.Magnitudes;
using Gamesmiths.Forge.Effects.Modifiers;
using Gamesmiths.Forge.Effects.Periodic;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Movementtests.tools.effects;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/white/icon_liquid.png")]
public partial class RManaRegen(float manaRegenRate, float frequency) : Resource
{
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
public float ManaRegenRate { get; set; } = manaRegenRate;
[Export(PropertyHint.Range, "0.01,1,0.01,or_greater")]
public float Frequency { get; set; } = frequency;
public RManaRegen() : this(1.0f, 0.1f)
{
}
public EffectData ManaRegen(TagsManager tagsManager)
{
return new EffectData(
"Mana Regen",
durationData: new DurationData(
DurationType.Infinite
),
modifiers: [
new Modifier(
"PlayerAttributeSet.Mana",
ModifierOperation.FlatBonus,
new ModifierMagnitude(
MagnitudeCalculationType.ScalableFloat,
new ScalableFloat(ManaRegenRate * Frequency))
)
],
cues: new []
{
new CueData(
CueTags: Tag.RequestTag(tagsManager, "cues.resources.mana").GetSingleTagContainer(),
MinValue: 0,
MaxValue: 100,
MagnitudeType: CueMagnitudeType.AttributeValueChange,
MagnitudeAttribute: "PlayerAttributeSet.Mana"
)
},
periodicData: new PeriodicData(
Period: new ScalableFloat(Frequency),
ExecuteOnApplication: true,
PeriodInhibitionRemovedPolicy: PeriodInhibitionRemovedPolicy.ResetPeriod
)
);
}
}

View File

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

View File

@@ -1,5 +0,0 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
}

View File

@@ -0,0 +1,13 @@
using Gamesmiths.Forge.Abilities;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Movementtests.interfaces;
public interface IAbilityBase
{
AbilityData Ability(TagsManager tagsManager, Node3D owner);
EffectData CostEffect(TagsManager tagsManager);
EffectData CooldownEffect(TagsManager tagsManager);
}

View File

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

View File

@@ -770,3 +770,10 @@ transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, -0.5, 0, 0)
[node name="PlayerFellRespawn" parent="." index="10" unique_id=479136076]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1.5, 0)
[node name="OmniLight3D" type="OmniLight3D" parent="." index="13" unique_id=702421172]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.5, 25, 4)
[node name="OmniLight3D2" type="OmniLight3D" parent="." index="14" unique_id=2016820716]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.5, 25, -9.5)
omni_range = 12.0

View File

@@ -15,8 +15,8 @@
[ext_resource type="PackedScene" uid="uid://qup00a7x2sji" path="res://scenes/fixed_dash_target/fixed_dashthrough_target.tscn" id="13_iq67o"]
[ext_resource type="PackedScene" uid="uid://b8aet6m4m2i83" path="res://scenes/tuto_trigger/TutoTrigger.tscn" id="14_lthgu"]
[sub_resource type="BoxShape3D" id="BoxShape3D_jk7w8"]
size = Vector3(6.75, 8.25, 7.25)
[sub_resource type="BoxShape3D" id="BoxShape3D_lthgu"]
size = Vector3(7.5, 3.75, 10.25)
[node name="Main" unique_id=955321579 instance=ExtResource("1_k7f42")]
@@ -105,13 +105,12 @@ transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 27, 13.5, -9)
[node name="FixedDashthroughTarget3" parent="Targets" index="9" unique_id=1106453232 instance=ExtResource("13_iq67o")]
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 43, 6, -8.5)
[node name="Player" parent="." index="14" unique_id=1309399929]
transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, 3, 0, 0)
[node name="TutoTrigger10" parent="." index="18" unique_id=840713937 instance=ExtResource("14_lthgu")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.75, 0, 3)
[node name="TutoTrigger5" parent="." index="14" unique_id=840713937 instance=ExtResource("14_lthgu")]
tuto_text = "Try to survive!"
[node name="CollisionShape3D" type="CollisionShape3D" parent="TutoTrigger10" index="1" unique_id=2145030859]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.875, 1.125, -4.625)
shape = SubResource("BoxShape3D_jk7w8")
[node name="CollisionShape3D" type="CollisionShape3D" parent="TutoTrigger5" index="1" unique_id=1820790391]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.625, -1.875)
shape = SubResource("BoxShape3D_lthgu")
[node name="Player" parent="." index="15" unique_id=1309399929]
transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, 3, 0, 0)

View File

@@ -7,7 +7,7 @@ using Movementtests.systems.damage;
[GlobalClass]
public partial class RDamageModifier : Resource, IDamageable
{
public event Action<IDamageable, DamageRecord> DamageTaken;
public event Action<IDamageable, DamageRecord> DamageTaken = null!;
[Export]
public EDamageTypes DamageType { get; set;}

View File

@@ -5,11 +5,11 @@ using Movementtests.interfaces;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/white/icon_heart.png")]
public partial class CHealth : Node, IHealthable
{
public event Action<IHealthable, HealthChangedRecord> HealthChanged;
public event Action<IHealthable> HealthDepleted;
public event Action<IHealthable, HealthChangedRecord> HealthChanged = null!;
public event Action<IHealthable> HealthDepleted = null!;
[Export]
public RHealth RHealth { get; set; }
public RHealth RHealth { get; set; } = null!;
public float CurrentHealth { get; set; }

View File

@@ -3,14 +3,10 @@ using System;
using Movementtests.interfaces;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/white/icon_heart.png")]
public partial class RHealth : Resource
public partial class RHealth(float startingHealth) : Resource
{
[Export]
public float StartingHealth { get; set;}
public float StartingHealth { get; set;} = startingHealth;
public RHealth() : this(100.0f) {}
public RHealth(float startingHealth)
{
StartingHealth = startingHealth;
}
}

View File

@@ -5,9 +5,9 @@ using Movementtests.interfaces;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_wind.png")]
public partial class CKnockback : Node3D, IKnockbackable
{
[Export] public RKnockback RKnockback { get; set; }
[Export] public RKnockback RKnockback { get; set;} = null!;
private KnockbackRecord _knockbackRecord;
private KnockbackRecord _knockbackRecord = null!;
public void RegisterKnockback(KnockbackRecord knockbackRecord)
{

View File

@@ -6,9 +6,9 @@ namespace Movementtests.scenes.movement;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_path_follow.png")]
public partial class CGroundedMovement : Node3D, IMoveable
{
[Export] public RMovement RMovement { get; set; }
[Export] public RMovement RMovement { get; set; } = null!;
[Export] public RayCast3D WallInFrontRayCast { get; set; }
[Export] public RayCast3D WallInFrontRayCast { get; set; } = null!;
public Vector3 ComputeVelocity(MovementInputs inputs)

View File

@@ -1,7 +1,14 @@
using System;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Events;
using Gamesmiths.Forge.Tags;
using Godot;
using Movementtests.interfaces;
using Movementtests.scenes.enemies;
using Movementtests.scenes.player_controller.scripts;
using Movementtests.systems;
using Movementtests.tools;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_beetle.png")]
public partial class Enemy : CharacterBody3D,
@@ -13,40 +20,41 @@ public partial class Enemy : CharacterBody3D,
ISpawnable,
IKnockbackable,
ITargetable,
IStunnable
IStunnable,
IForgeEntity
{
// Signals and events
public event Action<IDamageable, DamageRecord> DamageTaken;
public event Action<IHealthable, HealthChangedRecord> HealthChanged;
public event Action<IHealthable> HealthDepleted;
public event Action<IDamageable, DamageRecord> DamageTaken = null!;
public event Action<IHealthable, HealthChangedRecord> HealthChanged = null!;
public event Action<IHealthable> HealthDepleted = null!;
// Public export components
[Export]
public Node3D Target { get; set;}
public Node3D Target { get; set; } = null!;
[Export]
public float EnemyHeight { get; set; } = 1f;
[ExportGroup("Health")]
[Export]
public RHealth RHealth { get; set; }
public RHealth RHealth { get; set; } = null!;
[Export]
public RDeathEffect[] DeathEffects { get; set; }
public IHealthable CHealth { get; set; }
public RDeathEffect[] DeathEffects { get; set; } = null!;
public IHealthable CHealth { get; set; } = null!;
[ExportGroup("Damage")]
[Export]
public RDamage RDamage { get; set; }
public IDamageable CDamageable { get; set; }
public RDamage RDamage { get; set; } = null!;
public IDamageable CDamageable { get; set; } = null!;
[Export]
public RKnockback RKnockback { get; set; }
public IKnockbackable CKnockback { get; set; }
public RKnockback RKnockback { get; set; } = null!;
public IKnockbackable CKnockback { get; set; } = null!;
[ExportGroup("Movement")]
[Export]
public RMovement RMovement { get; set; }
public IMoveable CMovement { get; set; }
public RMovement RMovement { get; set; } = null!;
public IMoveable CMovement { get; set; } = null!;
// Public stuff
public float CurrentHealth
@@ -55,10 +63,16 @@ public partial class Enemy : CharacterBody3D,
set => CHealth.CurrentHealth = value;
}
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!;
// Private stuff
private Area3D _damageBox;
private Node3D _target;
private Healthbar _healthbar;
private Area3D _damageBox = null!;
internal Node3D _target = null!;
private Healthbar _healthbar = null!;
public override void _Ready()
{
@@ -71,6 +85,21 @@ public partial class Enemy : CharacterBody3D,
_damageBox = GetNode<Area3D>("DamageBox");
_target = GetNode<Node3D>("CTarget");
// Forge stuff
var forgeManager = GetTree().Root.GetNode<ForgeManager>("ForgeManager")!;
var baseTags = new TagContainer(
forgeManager.TagsManager,
[
Tag.RequestTag(forgeManager.TagsManager, "character.player"),
Tag.RequestTag(forgeManager.TagsManager, "class.warrior")
]);
Attributes = new EntityAttributes(new EnemyAttributeSet());
Tags = new EntityTags(baseTags);
EffectsManager = new EffectsManager(this, forgeManager.CuesManager);
Abilities = new(this);
Events = new();
CDamageable = (GetNode<Node>("CDamageable") as IDamageable)!;
CMovement = (GetNode<Node>("CMovement") as IMoveable)!;
CHealth = (GetNode<Node>("CHealth") as IHealthable)!;

View File

@@ -0,0 +1,16 @@
using Gamesmiths.Forge.Attributes;
namespace Movementtests.scenes.enemies;
public class EnemyAttributeSet : AttributeSet
{
public EntityAttribute Health { get; }
public EntityAttribute Strength { get; }
public EntityAttribute Speed { get; }
public EnemyAttributeSet()
{
Health = InitializeAttribute(nameof(Health), 100, 0, 150);
Strength = InitializeAttribute(nameof(Strength), 10, 0, 99);
Speed = InitializeAttribute(nameof(Speed), 5, 0, 10);
}
}

View File

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

View File

@@ -5,7 +5,9 @@
[ext_resource type="Script" uid="uid://jitubgv6judn" path="res://scenes/components/damage/RDamage.cs" id="2_x835q"]
[ext_resource type="Script" uid="uid://b44cse62qru7j" path="res://scenes/components/knockback/RKnockback.cs" id="3_cb2lu"]
[ext_resource type="Resource" uid="uid://bl5crtu1gkrtr" path="res://inputs/base_mode/base_mode.tres" id="3_cresl"]
[ext_resource type="Resource" uid="uid://dtmhtlix2amme" path="res://scenes/player_controller/resources/player_mana_regen.tres" id="3_n24vh"]
[ext_resource type="PackedScene" uid="uid://c4ikbhojckpnc" path="res://scenes/components/health/CHealth.tscn" id="3_q7bng"]
[ext_resource type="Script" uid="uid://rux15j7q78e8" path="res://forge/abilities/RExplodingSwordThrow.cs" id="4_11013"]
[ext_resource type="Script" uid="uid://baiapod3csndf" path="res://scenes/components/health/RHealth.cs" id="4_abfq8"]
[ext_resource type="Resource" uid="uid://bjyd801wvverk" path="res://scenes/player_controller/resources/player_health.tres" id="4_m8gvy"]
[ext_resource type="Resource" uid="uid://cpdaw41ah5gic" path="res://inputs/base_mode/rotate_y.tres" id="4_rxwoh"]
@@ -17,6 +19,7 @@
[ext_resource type="Resource" uid="uid://t612lts1wi1s" path="res://inputs/base_mode/move_right.tres" id="6_q7bng"]
[ext_resource type="Script" uid="uid://cwbvxlfvmocc1" path="res://scenes/player_controller/scripts/StairsSystem.cs" id="7_bmt5a"]
[ext_resource type="Resource" uid="uid://brswsknpgwal2" path="res://inputs/base_mode/move_front.tres" id="7_m8gvy"]
[ext_resource type="Resource" uid="uid://7dpkk5rk3di5" path="res://scenes/player_controller/resources/forge/empowered_action.tres" id="7_qheee"]
[ext_resource type="PackedScene" uid="uid://bctpe34ddamg5" path="res://scenes/components/knockback/CKnockback.tscn" id="7_x835q"]
[ext_resource type="Resource" uid="uid://s1l0n1iitc6m" path="res://inputs/base_mode/move_back.tres" id="8_jb43f"]
[ext_resource type="Resource" uid="uid://j1o5ud0plk4" path="res://inputs/base_mode/aim_release.tres" id="8_lhb11"]
@@ -53,6 +56,12 @@
[ext_resource type="Texture2D" uid="uid://c40orhfdgsim" path="res://assets/ui/IconGodotNode/white/icon_circle.png" id="45_u8rdp"]
[ext_resource type="PackedScene" uid="uid://cyw8p0p6a78tl" path="res://scenes/ui/healthbar/healthbar.tscn" id="47_76kmc"]
[sub_resource type="Resource" id="Resource_5b7hb"]
script = ExtResource("4_11013")
Explosion = ExtResource("5_ue7xq")
Cost = 10.0
metadata/_custom_type_script = "uid://rux15j7q78e8"
[sub_resource type="Resource" id="Resource_cb2lu"]
script = ExtResource("2_x835q")
DamageDealt = 10.0
@@ -109,9 +118,15 @@ radius = 1.5
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_2q0ik"]
blend_mode = 1
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n24vh"]
bg_color = Color(0.15869555, 0.64034444, 0.906125, 1)
[node name="Player" type="CharacterBody3D" unique_id=709076448]
collision_mask = 272
script = ExtResource("1_poq2x")
EmpoweredAction = ExtResource("7_qheee")
ManaRegen = ExtResource("3_n24vh")
AbilityLoadout = [SubResource("Resource_5b7hb")]
AimAssistStrength = 0.3
AimAssistReductionWhenCloseToTarget = 0.1
AimAssistReductionStartDistance = 8.0
@@ -223,12 +238,10 @@ debug_color = Color(0, 0.6, 0.701961, 0.341176)
[node name="HeadSystem" parent="." unique_id=1203743757 instance=ExtResource("11_rxwoh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
LookSensitivity = 0.16
CameraInclineAcceleration = 20.0
GroundedCameraIncline = 3.0
SlidingJitterAmplitude = 0.2
WeaponSway = 8.0
WeaponLookRotation = 10.0
WeaponAdjustmentSpeed = 1.0
[node name="MantleSystem" parent="HeadSystem" unique_id=98905505 instance=ExtResource("8_qu4wy")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.6, 0)
@@ -551,6 +564,22 @@ offset_bottom = -71.99939
grow_horizontal = 2
grow_vertical = 0
[node name="Manabar" parent="UI" unique_id=1713862004 instance=ExtResource("47_76kmc")]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -859.0
offset_top = -84.0
offset_right = -347.0
offset_bottom = -72.0
grow_horizontal = 2
grow_vertical = 0
BarStyle = SubResource("StyleBoxFlat_n24vh")
[node name="StateChart" type="Node" parent="." unique_id=1675830632]
script = ExtResource("25_wv70j")
metadata/_custom_type_script = "uid://couw105c3bde4"

View File

@@ -1,13 +1,18 @@
using Godot;
using System;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Tags;
using Movementtests.interfaces;
using Movementtests.tools;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/control/icon_text_panel.png")]
public partial class PlayerUi : Control
public partial class PlayerUi : Control, ICueHandler
{
private TextureRect[] _dashIcons = new TextureRect[3];
private TextureRect _enemyTarget;
private Healthbar _healthbar;
internal TextureRect[] DashIcons = new TextureRect[3];
private TextureRect _enemyTarget = null!;
private Healthbar _healthbar = null!;
private Healthbar _manabar = null!;
public enum TargetState
{
@@ -25,17 +30,24 @@ public partial class PlayerUi : Control
public override void _Ready()
{
_dashIcons[0] = GetNode<TextureRect>("%Dash1");
_dashIcons[1] = GetNode<TextureRect>("%Dash2");
_dashIcons[2] = GetNode<TextureRect>("%Dash3");
DashIcons[0] = GetNode<TextureRect>("%Dash1");
DashIcons[1] = GetNode<TextureRect>("%Dash2");
DashIcons[2] = GetNode<TextureRect>("%Dash3");
_enemyTarget = GetNode<TextureRect>("%EnemyTarget");
_healthbar = GetNode<Healthbar>("%Healthbar");
_manabar = GetNode<Healthbar>("%Manabar");
var forgeManager = GetTree().Root.GetNode<ForgeManager>("ForgeManager")!;
var tagsManager = forgeManager.TagsManager;
var cuesManager = forgeManager.CuesManager;
cuesManager.RegisterCue(Tag.RequestTag(tagsManager, "cues.resources.mana"), this);
}
public void Initialize(float initialHealth)
public void Initialize(float initialHealth, float initialMana)
{
_healthbar.Initialize(initialHealth);
_manabar.Initialize(initialMana);
}
public void SetEnemyTargetProperties(TargetProperties targetProperties)
@@ -59,7 +71,7 @@ public partial class PlayerUi : Control
public void SetNumberOfDashesLeft(int numberOfDashes)
{
int index = 1;
foreach (var dashIcon in _dashIcons)
foreach (var dashIcon in DashIcons)
{
dashIcon.SetVisible(index <= numberOfDashes);
index++;
@@ -70,4 +82,36 @@ public partial class PlayerUi : Control
{
_healthbar.CurrentHealth = healthChanged.CurrentHealth;
}
public void OnManaChanged(float newValue)
{
_manabar.CurrentHealth = newValue;
}
public void OnExecute(IForgeEntity? target, CueParameters? parameters)
{
// One-shot effect (like impact)
// Called when an instant effect with this cue is applied
// Also called when a periodic effect with this cue executes its period
if (target == null || !parameters.HasValue) return;
// Extract parameters
float magnitude = parameters.Value.Magnitude;
// Play effects scaled by magnitude
// PlayFireImpactSound(normalizedMagnitude);
// SpawnFireImpactParticles(target, magnitude);
_manabar.CurrentHealth += magnitude;
}
public void OnApply(IForgeEntity? target, CueParameters? parameters)
{
}
public void OnRemove(IForgeEntity? target, bool interrupted)
{
}
public void OnUpdate(IForgeEntity? target, CueParameters? parameters)
{
}
}

View File

@@ -24,21 +24,21 @@ public partial class DashSystem: Node3D
public bool ShouldMantle { get; set; }
public Vector3 PlannedMantleLocation { get; set; }
public MantleSystem MantleSystem { get; set; }
public MantleSystem MantleSystem { get; set; } = null!;
private HeadSystem Head;
public ShapeCast3D DashCast3D;
private Camera3D Camera;
private Vector3 DashDirection = Vector3.Zero;
internal HeadSystem Head = null!;
public ShapeCast3D DashCast3D = null!;
internal Camera3D Camera = null!;
internal Vector3 DashDirection = Vector3.Zero;
private ShapeCast3D DashCastDrop;
private MeshInstance3D DashDropIndicator;
private MeshInstance3D DashDropLocationIndicator;
private MeshInstance3D DashTarget;
private CpuParticles3D DashIndicator;
private AnimationPlayer DashIndicatorAnim;
internal ShapeCast3D DashCastDrop = null!;
internal MeshInstance3D DashDropIndicator = null!;
internal MeshInstance3D DashDropLocationIndicator = null!;
internal MeshInstance3D DashTarget = null!;
internal CpuParticles3D DashIndicator = null!;
internal AnimationPlayer DashIndicatorAnim = null!;
[Export] public PackedScene DashIndicatorScene { get; set; }
[Export] public PackedScene DashIndicatorScene { get; set; } = null!;
[Signal]
public delegate void DashStartedEventHandler();
@@ -75,7 +75,7 @@ public partial class DashSystem: Node3D
DashIndicatorAnim = GetNode<AnimationPlayer>("DashIndicator/AnimationPlayer");
}
private DashLocation ComputeDashLocation()
internal DashLocation ComputeDashLocation()
{
var targetLocation = DashCast3D.ToGlobal(DashCast3D.TargetPosition);
var hasHit = DashCast3D.IsColliding();

View File

@@ -39,12 +39,12 @@ public partial class HeadSystem : Node3D
float BobbingMultiplier,
float FovMultiplier);
private Camera3D _camera;
private Marker3D _cameraAnchor;
private AnimationPlayer _animationPlayer;
private AnimationTree _animationTree;
internal Camera3D Camera = null!;
internal Node3D CameraAnchor = null!;
internal AnimationPlayer AnimationPlayer = null!;
internal AnimationTree AnimationTree = null!;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
public float LookSensitivity { get; set; } = 1f;
[ExportGroup("Camera incline")]
@@ -63,11 +63,11 @@ public partial class HeadSystem : Node3D
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
public float SlidingJitterAmplitude { get; set; } = 0.1f;
private FastNoiseLite _slidingNoise = new FastNoiseLite();
internal FastNoiseLite _slidingNoise = new FastNoiseLite();
[ExportGroup("Bobbing")]
private float _bobbingAccumulator; // Constantly increases when player moves in X or/and Z axis
internal float _bobbingAccumulator; // Constantly increases when player moves in X or/and Z axis
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
public float BobbingFrequency { set; get; } = 2.4f;
[Export(PropertyHint.Range, "0,0.4,0.01,or_greater")]
@@ -84,78 +84,75 @@ public partial class HeadSystem : Node3D
public float FovMaxedOutSpeed { get; set; } = 20f;
[ExportGroup("First Person rig")]
private Node3D _fpRig;
private Node3D _rightHandedWeapon;
private Node3D _leftHandedWeapon;
private Node3D _fpDisplacedRig;
private Vector3 _fpDisplacedRigInitialRotation;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float WeaponSway { get; set; } = 5f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float WeaponLookRotation { get; set; } = 1f;
internal Node3D FpRig = null!;
internal Node3D RightHandedWeapon = null!;
internal Vector3 RightHandedWeaponInitialRotation = Vector3.Zero;
internal Node3D LeftHandedWeapon = null!;
internal Vector3 LeftHandedWeaponInitialRotation = Vector3.Zero;
[Export(PropertyHint.Range, "0,20,1,or_greater")]
public float WeaponSway { get; set; } = 15f;
[Export(PropertyHint.Range, "0,200,1,or_greater")]
public float WeaponMoveRotation { get; set; } = 80f;
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float WeaponAdjustmentSpeed { get; set; } = 10f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float DisplacedWeaponSway { get; set; } = 5f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float DisplacedWeaponLookRotation { get; set; } = 1f;
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
[Export(PropertyHint.Range, "0,20,1,or_greater")]
public float WeaponAdjustmentSpeed { get; set; } = 1f;
[Export(PropertyHint.Range, "0,2,0.01,or_greater")]
public float DisplacedWeaponSway { get; set; } = 0.8f;
[Export(PropertyHint.Range, "0,0.5,0.01,or_greater")]
public float DisplacedWeaponMoveRotation { get; set; } = 0.1f;
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float DisplacedWeaponAdjustmentSpeed { get; set; } = 10f;
[Export(PropertyHint.Range, "0,20,1,or_greater")]
public float DisplacedWeaponAdjustmentSpeed { get; set; } = 12f;
public void Init()
{
_isPlayingForcingAnim = false;
IsPlayingForcingAnim = false;
Input.SetMouseMode(Input.MouseModeEnum.Captured);
_camera = GetNode<Camera3D>("CameraSmooth/Camera3D");
_cameraAnchor = GetNode<Marker3D>("CameraAnchor");
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
_animationTree = GetNode<AnimationTree>("AnimationTree");
_fpRig = GetNode<Node3D>("FPRig");
_rightHandedWeapon = GetNode<Node3D>("FPRig/Sword");
_leftHandedWeapon = GetNode<Node3D>("FPRig/Parry");
_fpDisplacedRig = GetNode<Node3D>("FPRig/Sword");
_fpDisplacedRigInitialRotation = _fpDisplacedRig.Rotation;
Camera = GetNode<Camera3D>("CameraSmooth/Camera3D");
CameraAnchor = GetNode<Node3D>("CameraSmooth/CameraAnchor");
//_cameraAnchor = GetNode<Camera3D>("CameraSmooth/Camera3D");
AnimationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
AnimationTree = GetNode<AnimationTree>("AnimationTree");
FpRig = GetNode<Node3D>("FPRig");
RightHandedWeapon = GetNode<Node3D>("FPRig/Sword/SwordMesh");
RightHandedWeaponInitialRotation = RightHandedWeapon.Rotation;
LeftHandedWeapon = GetNode<Node3D>("FPRig/Parry/ParryMesh");
LeftHandedWeaponInitialRotation = LeftHandedWeapon.Rotation;
_slidingNoise.NoiseType = FastNoiseLite.NoiseTypeEnum.Perlin;
_slidingNoise.SetFrequency(SlidingJitterFrequency);
}
public void SetWeaponsVisible(bool swordVisible, bool parryVisible)
{
_rightHandedWeapon.Visible = swordVisible;
_leftHandedWeapon.Visible = parryVisible;
RightHandedWeapon.Visible = swordVisible;
LeftHandedWeapon.Visible = parryVisible;
}
public void OnMantle()
{
_animationTree.Set("parameters/OnMantle/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
AnimationTree.Set("parameters/OnMantle/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
public void OnJumpStarted()
{
_animationTree.Set("parameters/OnJumpStart/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
AnimationTree.Set("parameters/OnJumpStart/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
public void OnJumpEnded()
{
_animationTree.Set("parameters/OnJumpEnd/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
AnimationTree.Set("parameters/OnJumpEnd/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
public void OnHit()
{
_animationTree.Set("parameters/OnHit/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
AnimationTree.Set("parameters/OnHit/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
public void OnParry()
{
_animationTree.Set("parameters/OnParry/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
AnimationTree.Set("parameters/OnParry/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
public void OnStartDeathAnimation()
{
_isPlayingForcingAnim = true;
_animationTree.Set("parameters/OnDie/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
IsPlayingForcingAnim = true;
AnimationTree.Set("parameters/OnDie/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
public void OnDeathAnimationFinished()
@@ -190,8 +187,8 @@ public partial class HeadSystem : Node3D
EmitSignalHitboxDeactivated();
}
private bool _footstepEmitted;
private bool _isPlayingForcingAnim;
internal bool FootstepEmitted;
internal bool IsPlayingForcingAnim;
public void ResetHeadBobbing()
{
@@ -200,10 +197,10 @@ public partial class HeadSystem : Node3D
public void LookAround(CameraParameters inputs)
{
if (_isPlayingForcingAnim)
if (IsPlayingForcingAnim)
{
_camera.Position = Vector3.Zero;
_camera.Rotation = Vector3.Zero;
Camera.Position = Vector3.Zero;
Camera.Rotation = Vector3.Zero;
return;
}
@@ -223,7 +220,7 @@ public partial class HeadSystem : Node3D
RotateY(angleForHorizontalRotation);
// Vertical movement of head
Vector3 currentCameraRotation = _cameraAnchor.Rotation;
Vector3 currentCameraRotation = CameraAnchor.Rotation;
currentCameraRotation.X += Convert.ToSingle(lookDir.Y * LookSensitivity * sensitivitMultiplier);
currentCameraRotation.X = Mathf.Clamp(currentCameraRotation.X, Mathf.DegToRad(-90f), Mathf.DegToRad(90f));
@@ -242,18 +239,18 @@ public partial class HeadSystem : Node3D
cameraIncline = Mathf.DegToRad(GroundedCameraIncline * cameraInclineFactor * -1.0f);
}
currentCameraRotation.Z = (float) Mathf.Lerp(currentCameraRotation.Z, cameraIncline, delta * CameraInclineAcceleration);
_cameraAnchor.Rotation = currentCameraRotation;
CameraAnchor.Rotation = currentCameraRotation;
if (withCameraJitter)
{
_cameraAnchor.Position = Vector3.Down*SlidingCameraHeightOffset;
CameraAnchor.Position = Vector3.Down*SlidingCameraHeightOffset;
float noise1D = _slidingNoise.GetNoise1D(Time.GetTicksMsec());
float noiseAmplitude = SlidingJitterAmplitude*Mathf.Clamp(playerVelocity.Length(), 0f, 1f);
_cameraAnchor.Position += Vector3.Up*noise1D*noiseAmplitude;
CameraAnchor.Position += Vector3.Up*noise1D*noiseAmplitude;
}
else
{
_cameraAnchor.Position = Vector3.Zero;
CameraAnchor.Position = Vector3.Zero;
}
Vector3 newPositionForCamera = Vector3.Zero;
@@ -268,76 +265,79 @@ public partial class HeadSystem : Node3D
newPositionForCamera.Y = Mathf.Sin(_bobbingAccumulator * BobbingFrequency) * BobbingAmplitude * bobbingMultiplier;
newPositionForCamera.X = Mathf.Cos(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * bobbingMultiplier;
if (newPositionForCamera.Y < -0.07 && !_footstepEmitted) Footstep();
if (newPositionForCamera.Y > 0) _footstepEmitted = false;
if (newPositionForCamera.Y < -0.07 * bobbingMultiplier && !FootstepEmitted) Footstep();
if (newPositionForCamera.Y > 0) FootstepEmitted = false;
// Offset bobbing for weapon rig
newPositionForRig.Y = Mathf.Cos(_bobbingAccumulator * BobbingFrequency) * BobbingAmplitude * bobbingMultiplier * 0.2f;
newPositionForRig.X = Mathf.Sin(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * bobbingMultiplier * 0.2f;
}
_cameraAnchor.Position += newPositionForCamera;
_camera.GlobalTransform = _cameraAnchor.GetGlobalTransformInterpolated();
CameraAnchor.Position += newPositionForCamera;
Camera.GlobalTransform = CameraAnchor.GetGlobalTransformInterpolated();
// First person rig adjustments
_fpRig.GlobalTransform = _cameraAnchor.GetGlobalTransformInterpolated();
FpRig.GlobalTransform = Camera.GlobalTransform;
// Apply bobbing
_fpRig.Position += newPositionForRig;
FpRig.Position += newPositionForRig;
// Rotate the whole rig based on movement input
var newRigRotation = _fpRig.Rotation;
var camTilt = Mathf.Lerp(_fpRig.Rotation.Z, cameraIncline*WeaponMoveRotation, delta*WeaponAdjustmentSpeed);
var newRigRotation = FpRig.Rotation;
var camTilt = Mathf.Lerp(FpRig.Rotation.Z, CameraAnchor.Rotation.Z*WeaponMoveRotation, delta * WeaponAdjustmentSpeed);
newRigRotation.Z = (float) camTilt;
// Rotate the whole rig based on camera rotation input
newRigRotation.X = Mathf.Lerp(newRigRotation.X, -lookDir.Y*WeaponSway, (float) delta*WeaponAdjustmentSpeed);
newRigRotation.Y = Mathf.Lerp(newRigRotation.Y, -lookDir.X*WeaponSway, (float) delta*WeaponAdjustmentSpeed);
newRigRotation.X = Mathf.Lerp(newRigRotation.X, -lookDir.Y*WeaponSway, (float) delta * WeaponAdjustmentSpeed);
newRigRotation.Y = Mathf.Lerp(newRigRotation.Y, -lookDir.X*WeaponSway, (float) delta * WeaponAdjustmentSpeed);
// Apply
_fpRig.Rotation = newRigRotation;
FpRig.Rotation = newRigRotation;
// Compute displaced rig adjustments, starting with movement input
var newDisplacedRigRotation = _fpDisplacedRig.Rotation;
var howMuchForward = ComputeHowMuchInputForward(playerInput);
var howMuchSideways = ComputeHowMuchInputSideways(playerInput);
var displacedCamTiltForward = Mathf.Lerp(newDisplacedRigRotation.Z,
_fpDisplacedRigInitialRotation.Z + howMuchForward*DisplacedWeaponMoveRotation,
delta*DisplacedWeaponAdjustmentSpeed);
var displacedCamTiltSide = Mathf.Lerp(newDisplacedRigRotation.X,
_fpDisplacedRigInitialRotation.X - howMuchSideways*DisplacedWeaponMoveRotation,
delta*DisplacedWeaponAdjustmentSpeed);
newDisplacedRigRotation.X = (float) displacedCamTiltSide;
newDisplacedRigRotation.Z = (float) displacedCamTiltForward;
var displacedSwayY = Mathf.Lerp(newDisplacedRigRotation.Y,
_fpDisplacedRigInitialRotation.Y - lookDir.X*DisplacedWeaponSway,
delta*DisplacedWeaponAdjustmentSpeed);
newDisplacedRigRotation.Y = (float) displacedSwayY;
// Apply
_fpDisplacedRig.Rotation = newDisplacedRigRotation;
// Compute sword meshes procedural adjustments
RightHandedWeapon.Rotation = ComputeRotationForFpMesh(RightHandedWeapon, RightHandedWeaponInitialRotation, playerInput, lookDir, (float) delta);
LeftHandedWeapon.Rotation = ComputeRotationForFpMesh(LeftHandedWeapon, LeftHandedWeaponInitialRotation, playerInput, lookDir, (float) delta);
// Camera adjustments
float velocityClamped = Mathf.Clamp(playerVelocity.Length(), 0.5f, FovMaxedOutSpeed);
float targetFov = BaseFov + FovChangeFactor * velocityClamped * fovMultiplier;
_camera.Fov = Mathf.Lerp(_camera.Fov, targetFov, (float) delta * FovChangeSpeed);
Camera.Fov = Mathf.Lerp(Camera.Fov, targetFov, (float) delta * FovChangeSpeed);
}
public Vector3 ComputeRotationForFpMesh(Node3D mesh, Vector3 initialRotation, Vector3 playerInput, Vector2 lookDir, float delta)
{
var newMeshRotation = mesh.Rotation;
var howMuchForward = ComputeHowMuchInputForward(playerInput);
var howMuchSideways = ComputeHowMuchInputSideways(playerInput);
var displacedCamTiltForward = Mathf.Lerp(newMeshRotation.Z,
initialRotation.Z + howMuchForward*DisplacedWeaponMoveRotation,
delta * DisplacedWeaponAdjustmentSpeed);
var displacedCamTiltSide = Mathf.Lerp(newMeshRotation.X,
initialRotation.X - howMuchSideways*DisplacedWeaponMoveRotation,
delta * DisplacedWeaponAdjustmentSpeed);
newMeshRotation.X = displacedCamTiltSide;
newMeshRotation.Z = displacedCamTiltForward;
var displacedSwayY = Mathf.Lerp(newMeshRotation.Y,
initialRotation.Y - lookDir.X*DisplacedWeaponSway,
delta * DisplacedWeaponAdjustmentSpeed);
newMeshRotation.Y = displacedSwayY;
return newMeshRotation;
}
public void Footstep()
{
_footstepEmitted = true;
FootstepEmitted = true;
EmitSignalStepFoot();
}
public void HideWeapon()
{
_rightHandedWeapon.Visible = false;
RightHandedWeapon.Visible = false;
}
public void ShowWeapon()
{
_rightHandedWeapon.Visible = true;
RightHandedWeapon.Visible = true;
}
public float ComputeCameraInclineFactor(Vector3 direction)
@@ -368,15 +368,15 @@ public partial class HeadSystem : Node3D
public Vector3 GetGlobalForwardVector()
{
return _camera.GlobalBasis.Z;
return Camera.GlobalBasis.Z;
}
public Vector3 GetGlobalLookRotation()
{
return new Vector3(
_camera.Rotation.X,
Camera.Rotation.X,
Rotation.Y,
_camera.Rotation.Z);
Camera.Rotation.Z);
}
public void SetHeight(float height)

View File

@@ -654,9 +654,6 @@ _data = {
[node name="HeadSystem" type="Node3D" unique_id=2067407038]
script = ExtResource("1_8abgy")
WeaponMoveRotation = 20.0
DisplacedWeaponSway = 1.0
DisplacedWeaponAdjustmentSpeed = 8.0
[node name="FPRig" type="Node3D" parent="." unique_id=922968399]
transform = Transform3D(0.9999998, 0, 0, 0, 1.0000002, 0, 0, 0, 1.0000002, 0, 0, 0)
@@ -678,6 +675,9 @@ mesh = ExtResource("3_1ay6d")
[node name="CameraSmooth" type="Node3D" parent="." unique_id=2072010960]
transform = Transform3D(0.9999998, -0.00011616429, 0, 0.00011616431, 0.99999964, 0, 0, 0, 0.99999976, 0, 0, 0)
[node name="CameraAnchor" type="Marker3D" parent="CameraSmooth" unique_id=1554357312]
transform = Transform3D(1.0000002, 0.00011616435, 0, -0.000116164374, 1.0000004, 0, 0, 0, 1.0000002, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="CameraSmooth" unique_id=544372058]
transform = Transform3D(1, 0, 0, 0, 1.0000002, 0, 0, 0, 1.0000001, 0, 0, 0)
current = true
@@ -702,8 +702,6 @@ fade_out = 0.5547845
shakerPreset = SubResource("Resource_se3kf")
metadata/_custom_type_script = "uid://dnlxsrumw6ygp"
[node name="CameraAnchor" type="Marker3D" parent="." unique_id=1554357312]
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1831491746]
root_node = NodePath("../CameraSmooth/Camera3D")
libraries/ = SubResource("AnimationLibrary_0hyrq")

View File

@@ -0,0 +1,13 @@
using Gamesmiths.Forge.Attributes;
namespace Movementtests.scenes.player_controller.components.weapon;
public class WeaponAttributeSet : AttributeSet
{
public EntityAttribute Level { get; }
public WeaponAttributeSet()
{
Level = InitializeAttribute(nameof(Level), 1, 1, 10);
}
}

View File

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

View File

@@ -1,13 +1,21 @@
using System;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Events;
using Gamesmiths.Forge.Tags;
using Godot;
using GodotStateCharts;
using Movementtests.interfaces;
using Movementtests.scenes.player_controller.components.weapon;
using Movementtests.systems.damage;
using Movementtests.tools;
namespace Movementtests.systems;
public record struct WeaponLandPayload(int Damage, bool IsCritical);
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_sword.png")]
public partial class WeaponSystem : RigidBody3D, IDamageDealer
public partial class WeaponSystem : RigidBody3D, IDamageDealer, IForgeEntity
{
[Signal]
public delegate void WeaponThrownEventHandler();
@@ -22,11 +30,17 @@ public partial class WeaponSystem : RigidBody3D, IDamageDealer
[Export(PropertyHint.Range, "0,0.2,0.01,or_greater")]
public float StraightThrowDuration { get; set; } = 0.1f;
private StateChart _weaponState;
public StateChartState InHandState;
public StateChartState FlyingState;
public StateChartState PlantedState;
private ShapeCast3D _dashCast3D;
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!;
private StateChart _weaponState = null!;
public StateChartState InHandState = null!;
public StateChartState FlyingState = null!;
public StateChartState PlantedState = null!;
private ShapeCast3D _dashCast3D = null!;
private Transform3D _startTransform;
private Vector3 _startMeshRotation;
@@ -36,9 +50,11 @@ public partial class WeaponSystem : RigidBody3D, IDamageDealer
public Vector3 PlantNormal { get; set; }
public Node? PlantObject { get; set; }
public MeshInstance3D WeaponLocationIndicator { get; set; }
public StandardMaterial3D WeaponLocationIndicatorMaterial { get; set; }
public MeshInstance3D WeaponMesh { get; set; }
public MeshInstance3D WeaponLocationIndicator { get; set; } = null!;
public StandardMaterial3D WeaponLocationIndicatorMaterial { get; set; } = null!;
public MeshInstance3D WeaponMesh { get; set; } = null!;
public Tag WeaponLandTag;
public void Init()
{
@@ -57,6 +73,22 @@ public partial class WeaponSystem : RigidBody3D, IDamageDealer
_startTransform = Transform;
Freeze = true;
Visible = false;
var tagsManager = ForgeManager.GetTagsManager(this);
var cuesManager = ForgeManager.GetCuesManager(this);
var baseTags = new TagContainer(
tagsManager,
[
Tag.RequestTag(tagsManager, "weapon")
]);
Attributes = new EntityAttributes(new WeaponAttributeSet());
Tags = new EntityTags(baseTags);
EffectsManager = new EffectsManager(this, cuesManager);
Abilities = new(this);
Events = new();
WeaponLandTag = Tag.RequestTag(tagsManager, "events.weapon.land");
BodyEntered += OnThrownWeaponReachesGround;
@@ -109,11 +141,26 @@ public partial class WeaponSystem : RigidBody3D, IDamageDealer
tween.Finished += ThrowWeaponOnCurve;
}
public void RaiseWeaponLandEvent(IForgeEntity? victim = null)
{
Events.Raise(new EventData<WeaponLandPayload>
{
EventTags = WeaponLandTag.GetSingleTagContainer(),
Source = this,
Target = victim,
EventMagnitude = 25f,
Payload = new WeaponLandPayload(Damage: 25, IsCritical: true)
});
}
public void PlantInEnemy(Node3D enemy)
{
GetTree().GetRoot().CallDeferred(Node.MethodName.RemoveChild, this);
enemy.CallDeferred(Node.MethodName.AddChild, this);
if (enemy is IForgeEntity victim) RaiseWeaponLandEvent(victim);
else RaiseWeaponLandEvent();
if (enemy is IDamageable damageable)
{
damageable.TakeDamage(new DamageRecord(GlobalPosition, RDamage));
@@ -145,6 +192,8 @@ public partial class WeaponSystem : RigidBody3D, IDamageDealer
{
PlantInEnemy(node);
}
else RaiseWeaponLandEvent();
CallDeferred(Node3D.MethodName.SetGlobalPosition, PlantLocation);
CallDeferred(Node3D.MethodName.LookAt, GlobalTransform.Origin + PlantNormal, Vector3.Up, true);
}

View File

@@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="REmpoweredAction" format=3 uid="uid://7dpkk5rk3di5"]
[ext_resource type="Script" uid="uid://d0l07gcx1ef18" path="res://forge/abilities/REmpoweredAction.cs" id="1_1rxoq"]
[resource]
script = ExtResource("1_1rxoq")
Cost = 30.0
Cooldown = 0.5
ManaRegenPause = 2.0
metadata/_custom_type_script = "uid://d0l07gcx1ef18"

View File

@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="RExplodingSwordThrow" format=3 uid="uid://cdxbwirfiaipi"]
[ext_resource type="Script" uid="uid://rux15j7q78e8" path="res://forge/abilities/RExplodingSwordThrow.cs" id="1_5iq8v"]
[resource]
script = ExtResource("1_5iq8v")
Cost = 20.0
metadata/_custom_type_script = "uid://rux15j7q78e8"

View File

@@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="RManaRegen" format=3 uid="uid://dtmhtlix2amme"]
[ext_resource type="Script" uid="uid://di04jvuqp0h7m" path="res://forge/effects/RManaRegen.cs" id="1_ecb1p"]
[resource]
script = ExtResource("1_ecb1p")
ManaRegenRate = 20.0
Frequency = 0.05
metadata/_custom_type_script = "uid://di04jvuqp0h7m"

View File

@@ -11,7 +11,6 @@ public class PlayerAttributeSet : AttributeSet
public PlayerAttributeSet()
{
// Initialize the attributes with the current, min and max values.
Health = InitializeAttribute(nameof(Health), 100, 0, 150);
Mana = InitializeAttribute(nameof(Mana), 100, 0, 100);
Strength = InitializeAttribute(nameof(Strength), 10, 0, 99);

View File

@@ -1,17 +1,25 @@
using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Abilities;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Effects.Components;
using Gamesmiths.Forge.Effects.Duration;
using Gamesmiths.Forge.Effects.Magnitudes;
using Gamesmiths.Forge.Events;
using Gamesmiths.Forge.Tags;
using Godot;
using GodotStateCharts;
using Movementtests.addons.godot_state_charts.csharp;
using Movementtests.interfaces;
using Movementtests.systems;
using Movementtests.player_controller.Scripts;
using Movementtests.scenes.player_controller.scripts;
using Movementtests.tools;
using Movementtests.forge.abilities;
using Movementtests.tools.effects;
using RustyOptions;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_character.png")]
@@ -49,49 +57,59 @@ public partial class PlayerController : CharacterBody3D,
[Signal]
public delegate void PlayerDiedEventHandler();
public event Action<IDamageable, DamageRecord> DamageTaken;
public event Action<IHealthable, HealthChangedRecord> HealthChanged;
public event Action<IHealthable> HealthDepleted;
public event Action<IDamageable, DamageRecord> DamageTaken = null!;
public event Action<IHealthable, HealthChangedRecord> HealthChanged = null!;
public event Action<IHealthable> HealthDepleted = null!;
///////////////////////////
// Public stuff //
///////////////////////////
public HeadSystem HeadSystem;
public StairsSystem StairsSystem;
public MantleSystem MantleSystem;
public DashSystem DashSystem;
public CollisionShape3D StandingCollider;
public CollisionShape3D SlideCollider;
public WeaponSystem WeaponSystem;
public WallHugSystem WallHugSystem;
public PlayerUi PlayerUi;
public TextureRect DashIndicator;
public ColorRect PowerCooldownIndicator;
public Node3D DashIndicatorNode;
public MeshInstance3D DashIndicatorMesh;
public CylinderMesh DashIndicatorMeshCylinder;
public RayCast3D WallRunSnapper;
public ShapeCast3D GroundDetector;
public ShapeCast3D CeilingDetector;
public RayCast3D DirectGroundDetector;
public Area3D WeaponHitbox;
public AudioStreamPlayer3D SfxPlayer;
public HeadSystem HeadSystem = null!;
public StairsSystem StairsSystem = null!;
public MantleSystem MantleSystem = null!;
public DashSystem DashSystem = null!;
public CollisionShape3D StandingCollider = null!;
public CollisionShape3D SlideCollider = null!;
public WeaponSystem WeaponSystem = null!;
public WallHugSystem WallHugSystem = null!;
public PlayerUi PlayerUi = null!;
public TextureRect DashIndicator = null!;
public ColorRect PowerCooldownIndicator = null!;
public Node3D DashIndicatorNode = null!;
public MeshInstance3D DashIndicatorMesh = null!;
public CylinderMesh DashIndicatorMeshCylinder = null!;
public RayCast3D WallRunSnapper = null!;
public ShapeCast3D GroundDetector = null!;
public ShapeCast3D CeilingDetector = null!;
public RayCast3D DirectGroundDetector = null!;
public Area3D WeaponHitbox = null!;
public AudioStreamPlayer3D SfxPlayer = null!;
public ShapeCast3D DashDamageDetector;
public Area3D SlidingEnemyDetector;
public ShapeCast3D DashDamageDetector = null!;
public Area3D SlidingEnemyDetector = null!;
public EntityAttributes Attributes { get; set; }
public EntityTags Tags { get; set; }
public EffectsManager EffectsManager { get; set; }
public EntityAbilities Abilities { get; set; }
public EventManager Events { get; set; }
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!;
// Inspector stuff
[Export] public Marker3D TutorialWeaponTarget;
[Export] public Marker3D TutorialWeaponTarget = null!;
[Export] public bool TutorialDone { get; set; }
[Export] public bool HasSword { get; set; } = true;
[Export] public bool HasParry { get; set; } = true;
// Forge stuff
[ExportCategory("Forge")]
[ExportGroup("General")]
[Export] public REmpoweredAction EmpoweredAction = null!;
[Export] public RManaRegen ManaRegen = null!;
[ExportGroup("Abilities")]
[ExportSubgroup("WeaponThrow")]
[Export] public RAbilityBase[] AbilityLoadout = [];
// Combat stuff
[ExportCategory("Combat")]
[ExportGroup("General")]
@@ -102,9 +120,9 @@ public partial class PlayerController : CharacterBody3D,
[Export(PropertyHint.Range, "0,10f,0.1,or_greater")]
public float AimAssistReductionStartDistance { get; set; } = 10f;
[ExportGroup("Damage")] [Export] public RDamage RDamage { get; set; }
[Export] public RKnockback? RKnockback { get; set; }
[Export] public RHealth? RHealth { get; set; }
[ExportGroup("Damage")] [Export] public RDamage RDamage { get; set; } = null!;
[Export] public RKnockback? RKnockback { get; set; } = null!;
[Export] public RHealth? RHealth { get; set; } = null!;
[ExportGroup("Targeting")]
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
@@ -114,7 +132,7 @@ public partial class PlayerController : CharacterBody3D,
[ExportGroup("Instantiation")]
[Export]
public PackedScene Explosion { get; set; }
public PackedScene Explosion { get; set; } = null!;
// Movement stuff
[ExportCategory("Movement")]
@@ -141,7 +159,7 @@ public partial class PlayerController : CharacterBody3D,
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
public float MantleTime { get; set; } = 0.1f;
[Export]
public PackedScene MantlePath { get; set; }
public PackedScene MantlePath { get; set; } = null!;
[Export(PropertyHint.Range, "0,50,0.1")]
public float MantleDashStrength { get; set; } = 15f;
@@ -266,8 +284,8 @@ public partial class PlayerController : CharacterBody3D,
// Stairs and shit
private float _lastFrameWasOnFloor = -Mathf.Inf;
private const int NumOfHeadCollisionDetectors = 4;
private RayCast3D[] _headCollisionDetectors;
private AudioStreamPlaybackInteractive _audioStream;
private RayCast3D[] _headCollisionDetectors = null!;
private AudioStreamPlaybackInteractive _audioStream = null!;
// Basic movement
private bool _movementEnabled = true;
@@ -277,8 +295,8 @@ public partial class PlayerController : CharacterBody3D,
private float _inputRotateFloorplane;
// Basic falling
private float TargetSpeed;
private float Gravity;
internal float TargetSpeed;
internal float Gravity;
// Jump stuff
private int _currentInputBufferFrames;
@@ -291,7 +309,7 @@ public partial class PlayerController : CharacterBody3D,
private Path? _mantlePath;
private bool _customMantle;
private Transform3D _customMantleStartTransform;
private Curve3D _customMantleCurve;
private Curve3D _customMantleCurve = null!;
private Vector3 _mantleStartPosition;
private Vector3 _velocityOnMantleStarted = Vector3.Zero;
@@ -302,7 +320,7 @@ public partial class PlayerController : CharacterBody3D,
private Vector3 _currentWallContactPoint = Vector3.Zero;
// Dash stuff
private bool CanDash = true;
internal bool CanDash = true;
private bool _canDashAirborne = true;
private float _playerHeight;
private float _playerRadius;
@@ -328,56 +346,56 @@ public partial class PlayerController : CharacterBody3D,
private float _aimAssistMultiplier = 1.0f;
// Timers
private Timer _timeScaleAimInAirTimer;
private Timer _weaponThrowUncatchableTimer;
private Timer _simpleDashCooldownTimer;
private Timer _airborneDashCooldownTimer;
private Timer _powerCooldownTimer;
private Timer _invincibilityTimer;
private Timer _attackCooldown;
private Timer _timeScaleAimInAirTimer = null!;
private Timer _weaponThrowUncatchableTimer = null!;
private Timer _simpleDashCooldownTimer = null!;
private Timer _airborneDashCooldownTimer = null!;
private Timer _powerCooldownTimer = null!;
private Timer _invincibilityTimer = null!;
private Timer _attackCooldown = null!;
// State chart
private StateChart _playerState;
private StateChart _playerState = null!;
private StateChartState _aiming;
private StateChartState _powerExpired;
private StateChartState _powerRecharging;
private StateChartState _powerFull;
private StateChartState _aiming = null!;
private StateChartState _powerExpired = null!;
private StateChartState _powerRecharging = null!;
private StateChartState _powerFull = null!;
private StateChartState _grounded;
private StateChartState _airborne;
private StateChartState _coyoteEnabled;
private StateChartState _jumping;
private StateChartState _simpleJump;
private StateChartState _doubleJump;
private StateChartState _mantling;
private StateChartState _simpleDash;
private StateChartState _aimedDash;
private StateChartState _weaponDash;
private StateChartState _sliding;
private StateChartState _groundSliding;
private StateChartState _airGliding;
private StateChartState _airGlidingDoubleJump;
private StateChartState _slideCanceled;
private StateChartState _slamming;
private StateChartState _onWall;
private StateChartState _onWallHugging;
private StateChartState _onWallHanging;
private StateChartState _onWallRunning;
private StateChartState _grounded = null!;
private StateChartState _airborne = null!;
private StateChartState _coyoteEnabled = null!;
private StateChartState _jumping = null!;
private StateChartState _simpleJump = null!;
private StateChartState _doubleJump = null!;
private StateChartState _mantling = null!;
private StateChartState _simpleDash = null!;
private StateChartState _aimedDash = null!;
private StateChartState _weaponDash = null!;
private StateChartState _sliding = null!;
private StateChartState _groundSliding = null!;
private StateChartState _airGliding = null!;
private StateChartState _airGlidingDoubleJump = null!;
private StateChartState _slideCanceled = null!;
private StateChartState _slamming = null!;
private StateChartState _onWall = null!;
private StateChartState _onWallHugging = null!;
private StateChartState _onWallHanging = null!;
private StateChartState _onWallRunning = null!;
private StateChartState _attackStandard;
private StateChartState _attackDash;
private StateChartState _parryStandard;
private StateChartState _parryDash;
private StateChartState _attackStandard = null!;
private StateChartState _attackDash = null!;
private StateChartState _parryStandard = null!;
private StateChartState _parryDash = null!;
private Transition _onJumpFromWall;
private Transition _onJumpFromWallFalling;
private Transition _onJumpFromWallRunning;
private Transition _onLeaveWallFromRun;
private Transition _onAirborneToGrounded;
private Transition _onJumpFromWall = null!;
private Transition _onJumpFromWallFalling = null!;
private Transition _onJumpFromWallRunning = null!;
private Transition _onLeaveWallFromRun = null!;
private Transition _onAirborneToGrounded = null!;
private Transition _onGroundSlideJump;
private Transition _onAirGlideDoubleJump;
private Transition _onGroundSlideJump = null!;
private Transition _onAirGlideDoubleJump = null!;
// Damage
public CDamageable? CDamageable { get; set; }
@@ -395,9 +413,12 @@ public partial class PlayerController : CharacterBody3D,
private readonly List<IDamageable> _hitEnemies = new List<IDamageable>();
private ShapeCast3D _closeEnemyDetector;
private RayCast3D _aimAssisRayCast;
private Camera3D _camera;
private ShapeCast3D _closeEnemyDetector = null!;
private RayCast3D _aimAssisRayCast = null!;
private Camera3D _camera = null!;
private AbilityHandle? _empoweredActionHandle;
private ActiveEffectHandle? _manaRegenEffectHandle;
public override void _Ready()
{
@@ -415,25 +436,35 @@ public partial class PlayerController : CharacterBody3D,
_aimAssisRayCast.TargetPosition = _aimAssisRayCast.TargetPosition.Normalized() * (TargetingDistance*1.5f);
// Forge stuff
var forgeManager = GetTree().Root.GetNode<ForgeManager>("ForgeManager")!;
var tagsManager = ForgeManager.GetTagsManager(this);
var cuesManager = ForgeManager.GetCuesManager(this);
var baseTags = new TagContainer(
forgeManager.TagsManager,
tagsManager,
[
Tag.RequestTag(forgeManager.TagsManager, "character.player"),
Tag.RequestTag(forgeManager.TagsManager, "class.warrior")
Tag.RequestTag(tagsManager, "character.player")
]);
Attributes = new EntityAttributes(new PlayerAttributeSet());
Tags = new EntityTags(baseTags);
EffectsManager = new EffectsManager(this, forgeManager.CuesManager);
EffectsManager = new EffectsManager(this, cuesManager);
Abilities = new(this);
Events = new();
var empoweredActionData = EmpoweredAction.Ability(tagsManager);
// Grant permanently
_empoweredActionHandle = Abilities.GrantAbilityPermanently(
empoweredActionData,
abilityLevel: 1,
levelOverridePolicy: LevelComparison.None,
sourceEntity: this);
var manaRegenEffect = new Effect(ManaRegen.ManaRegen(tagsManager), new EffectOwnership(this, this));
_manaRegenEffectHandle = EffectsManager.ApplyEffect(manaRegenEffect);
var health = Attributes["PlayerAttributeSet.Health"].CurrentValue; // 100
var mana = Attributes["PlayerAttributeSet.Mana"].CurrentValue; // 100
var strength = Attributes["PlayerAttributeSet.Strength"].CurrentValue; // 10
var speed = Attributes["PlayerAttributeSet.Speed"].CurrentValue; // 5
GD.Print(health, mana, strength, speed);
// DashIndicator = GetNode<TextureRect>("%DashIndicator");
PowerCooldownIndicator = GetNode<ColorRect>("%DashCooldownIndicator");
@@ -499,7 +530,7 @@ public partial class PlayerController : CharacterBody3D,
}
if (RKnockback != null) CKnockback!.RKnockback = RKnockback;
PlayerUi.Initialize(CHealth.CurrentHealth);
PlayerUi.Initialize(CHealth.CurrentHealth, Attributes["PlayerAttributeSet.Mana"].BaseValue);
CDamageable.DamageTaken += (damageable, record) => ReduceHealth(damageable, record);
CDamageable.DamageTaken += (_, record) => RegisterKnockback(new KnockbackRecord(record));
CHealth.HealthChanged += PlayerUi.OnHealthChanged;
@@ -673,9 +704,47 @@ public partial class PlayerController : CharacterBody3D,
_attackDash.StateEntered += OnDashAttackStarted;
_parryStandard.StateEntered += OnStandardParryStarted;
_parryDash.StateEntered += OnDashParryStarted;
foreach (var weaponLandAbility in AbilityLoadout)
{
var grantAbilityConfig = new GrantAbilityConfig(
weaponLandAbility.Ability(tagsManager, WeaponSystem),
ScalableLevel: new ScalableInt(1),
RemovalPolicy: AbilityDeactivationPolicy.CancelImmediately,
InhibitionPolicy: AbilityDeactivationPolicy.CancelImmediately,
TryActivateOnGrant: false,
TryActivateOnEnable: false,
LevelOverridePolicy: LevelComparison.Higher);
var grantComponent = new GrantAbilityEffectComponent([grantAbilityConfig]);
var grantEffect = new EffectData(
"Grant Weapon Land Ability",
new DurationData(DurationType.Infinite),
effectComponents: [grantComponent]);
EffectsManager.ApplyEffect(new Effect(grantEffect, new EffectOwnership(this, this)));
}
// Testing out kill
// GetTree().CreateTimer(2).Timeout += () => Kill(this);
// Forge events
EventSubscriptionToken token = WeaponSystem.Events.Subscribe<WeaponLandPayload>(WeaponSystem.WeaponLandTag, OnWeaponLanded);
}
public void OnWeaponLanded(EventData<WeaponLandPayload> data)
{
var source = data.Source;
var target = data.Target;
var magnitude = data.EventMagnitude;
var weaponLandPayload = data.Payload;
var tagsManager = ForgeManager.GetTagsManager(this);
var weaponLandTag = Tag.RequestTag(tagsManager, "abilities.weapon.land").GetSingleTagContainer();
if (weaponLandTag == null) return;
var anyActivated = Abilities.TryActivateAbilitiesByTag(
weaponLandTag,
target,
out var failures);
if (anyActivated)
{
}
}
///////////////////////////
@@ -735,7 +804,7 @@ public partial class PlayerController : CharacterBody3D,
{
RHealth.StartingHealth = newHealthValue;
CHealth!.CurrentHealth = newHealthValue;
PlayerUi.Initialize(CHealth.CurrentHealth);
PlayerUi.Initialize(CHealth.CurrentHealth, Attributes["PlayerAttributeSet.Mana"].BaseValue);
}
public void SetPlayerDamageOverride(float newDamageValue)
{
@@ -1913,10 +1982,53 @@ public partial class PlayerController : CharacterBody3D,
public bool CanPerformEmpoweredAction()
{
if(_empoweredActionHandle == null) return false;
var cooldowns = _empoweredActionHandle.GetCooldownData();
foreach (var cd in cooldowns)
{
//GD.Print($"Cooldown remaining: {cd.RemainingTime}");
}
var costs = _empoweredActionHandle.GetCostData();
foreach (var cost in costs)
{
// Assuming you want to check Mana costs
if (cost.Attribute == "PlayerAttributeSet.Mana")
{
//GD.Print($"Mana Cost: {cost.Cost}");
}
}
var canActivate = _empoweredActionHandle.CanActivate(out var failures);
if (!canActivate)
{
GD.PrintErr($"Cannot activate empowered action: {failures}");
if (failures.HasFlag(AbilityActivationFailures.Cooldown)) GD.PrintErr("In Cooldown");
if (failures.HasFlag(AbilityActivationFailures.InsufficientResources)) GD.PrintErr("Not Enough Mana");
}
return canActivate;
return EmpoweredActionsLeft > 0 && TutorialDone;
}
public void PerformEmpoweredAction()
{
if(_empoweredActionHandle == null) return;
var canActivate = _empoweredActionHandle.Activate(out var failures);
if (!canActivate)
{
GD.PrintErr($"Cannot activate empowered action: {failures}");
if (failures.HasFlag(AbilityActivationFailures.Cooldown)) GD.PrintErr("In Cooldown");
if (failures.HasFlag(AbilityActivationFailures.InsufficientResources)) GD.PrintErr("Not Enough Mana");
}
else
{
GD.Print($"Remaining mana: {Attributes["PlayerAttributeSet.Mana"].CurrentValue}");
}
// Inhibit Mana Regeneration for a while after using an empowered action
// TODO: Use Forge events instead of relying on direct referencing
_manaRegenEffectHandle!.SetInhibit(true);
GetTree().CreateTimer(EmpoweredAction.ManaRegenPause).Timeout += () => {_manaRegenEffectHandle!.SetInhibit(false);};
_isWallJumpAvailable = true;
_canDashAirborne = true;
EmpoweredActionsLeft--;
@@ -2223,14 +2335,10 @@ public partial class PlayerController : CharacterBody3D,
_spaceState = GetWorld3D().DirectSpaceState;
if (_currentInputBufferFrames > 0) _currentInputBufferFrames -= 1;
// Limit maximum speed
if (Velocity.Length() > AbsoluteMaxSpeed)
Velocity = Velocity.Normalized() * AbsoluteMaxSpeed;
// Manage head and camera movement
LookAround(delta);
// Manage general movement
Velocity += ComputeKnockback();
MoveSlideAndHandleStairs((float) delta);
@@ -2238,21 +2346,22 @@ public partial class PlayerController : CharacterBody3D,
// Manage gameplay systems
MantleSystem.ProcessMantle(_grounded.Active);
HandleEnemyTargeting();
// Manage dash target and tutorial specific stuff
// if (WeaponSystem.InHandState.Active && !_aiming.Active && TutorialDone)
// {
// DashIndicatorMesh.Visible = false;
// }
// if (!WeaponSystem.InHandState.Active && TutorialDone)
// {
// DashIndicatorMesh.Visible = true;
//
// DashIndicatorMeshCylinder.Height = WeaponSystem.GlobalPosition.DistanceTo(GlobalPosition) * 2;
// DashIndicatorNode.LookAt(WeaponSystem.GlobalPosition);
// }
}
// private float _oldMana = 100;
public override void _Process(double delta)
{
// Manage head and camera movement
LookAround(delta);
EffectsManager.UpdateEffects(delta);
// TODO: change for actual Cue
// var currentMana = Attributes["PlayerAttributeSet.Mana"].CurrentValue;
// if (Mathf.Abs(currentMana - _oldMana) > Mathf.Epsilon)
// PlayerUi.OnManaChanged(currentMana);
// _oldMana = currentMana;
}
///////////////////////////
// Hit Management ///////
///////////////////////////
@@ -2348,7 +2457,7 @@ public partial class PlayerController : CharacterBody3D,
_audioStream.SwitchToClipByName("parry");
}
private PhysicsDirectSpaceState3D _spaceState;
private PhysicsDirectSpaceState3D _spaceState = null!;
public void StartDashAction(bool isParry)
{
if (isParry) HeadSystem.OnParry();

View File

@@ -4,8 +4,10 @@ using System;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/control/icon_heart.png")]
public partial class Healthbar : ProgressBar
{
private Timer _damageCatchUpTimer;
private ProgressBar _damagedHealth;
private Timer _damageCatchUpTimer = null!;
private ProgressBar _damagedHealth = null!;
[Export] public StyleBox? BarStyle;
private float _currentHealth;
public float CurrentHealth
@@ -21,6 +23,9 @@ public partial class Healthbar : ProgressBar
_damageCatchUpTimer.Timeout += OnDamageCatchUp;
Visible = false;
if (BarStyle != null)
AddThemeStyleboxOverride("fill", BarStyle);
}
public void Initialize(float initialHealth)

View File

@@ -5,7 +5,7 @@
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_0sgot"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0k5hr"]
bg_color = Color(0.698864, 0.047356047, 0, 1)
bg_color = Color(0.69803923, 0.047058824, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0sgot"]
bg_color = Color(0.14767182, 0.14767182, 0.14767176, 1)

View File

@@ -0,0 +1,101 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.interfaces;
using Movementtests.systems.damage;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class DamageComponentUnitTest
{
[TestCase]
public void DamageModifierAppliesWhenTypeMatches()
{
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var modifier = new RDamageModifier(EDamageTypes.Normal, 2.0f);
var signalTriggered = false;
modifier.DamageTaken += (_, _) => signalTriggered = true;
var result = modifier.TakeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(20.0f);
AssertBool(signalTriggered).IsTrue();
}
[TestCase]
public void DamageModifierIgnoresWhenTypeDifferent()
{
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var modifier = new RDamageModifier(EDamageTypes.Fire, 3.0f);
var signalTriggered = false;
modifier.DamageTaken += (_, _) => signalTriggered = true;
var result = modifier.TakeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(0.0f);
AssertBool(signalTriggered).IsFalse(); // No damage actually taken
}
[TestCase]
public void DamageableSumsAllModifiers()
{
var mod1 = new RDamageModifier(EDamageTypes.Normal, 1.0f);
var mod2 = new RDamageModifier(EDamageTypes.Normal, 0.5f);
var damageable = new CDamageable();
damageable.DamageModifiers = new[] { mod1, mod2 };
var signalTriggered = false;
damageable.DamageTaken += (_, _) => signalTriggered = true;
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var result = damageable.TakeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(15.0f);
AssertBool(signalTriggered).IsTrue();
}
[TestCase]
public void ComputeDamageModifierAppliesWhenTypeMatches()
{
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var modifier = new RDamageModifier(EDamageTypes.Normal, 2.0f);
var signalTriggered = false;
modifier.DamageTaken += (_, _) => signalTriggered = true;
var result = modifier.ComputeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(20.0f);
AssertBool(signalTriggered).IsFalse();
}
[TestCase]
public void ComputeDamageModifierIgnoresWhenTypeDifferent()
{
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var modifier = new RDamageModifier(EDamageTypes.Fire, 3.0f);
var signalTriggered = false;
modifier.DamageTaken += (_, _) => signalTriggered = true;
var result = modifier.ComputeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(0.0f);
AssertBool(signalTriggered).IsFalse();
}
[TestCase]
public void ComputeDamageableSumsAllModifiers()
{
var mod1 = new RDamageModifier(EDamageTypes.Normal, 1.0f);
var mod2 = new RDamageModifier(EDamageTypes.Normal, 0.5f);
var cDamageable = new CDamageable();
cDamageable.DamageModifiers = new[] { mod1, mod2 };
var signalTriggered = false;
cDamageable.DamageTaken += (_, _) => signalTriggered = true;
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var result = cDamageable.ComputeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(15.0f);
AssertBool(signalTriggered).IsFalse();
}
}

View File

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

View File

@@ -0,0 +1,55 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.interfaces;
using Movementtests.systems.damage;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class HealthComponentUnitTest
{
[TestCase]
public void ReadyInitializesCurrentHealth()
{
var cHealth = new CHealth();
cHealth.RHealth = new RHealth(150.0f);
cHealth._Ready();
AssertFloat(cHealth.CurrentHealth).IsEqual(150.0f);
}
[TestCase]
public void ReduceHealthDecreasesAndDoesNotDeplete()
{
var cHealth = new CHealth();
cHealth.RHealth = new RHealth(100.0f);
cHealth.CurrentHealth = 100.0f;
var damage = new DamageRecord(Vector3.Zero, new RDamage(25.0f, EDamageTypes.Normal));
var record = cHealth.ReduceHealth(source: null!, damageRecord: damage);
AssertFloat(cHealth.CurrentHealth).IsEqual(75.0f);
AssertFloat(record.CurrentHealth).IsEqual(75.0f);
AssertFloat(record.PreviousHealth).IsEqual(100.0f);
AssertFloat(record.MaxHealth).IsEqual(100.0f);
}
[TestCase]
public void ReduceHealthTriggersDepletionToZero()
{
var cHealth = new CHealth();
cHealth.RHealth = new RHealth(50.0f);
cHealth.CurrentHealth = 50.0f;
bool depleted = false;
cHealth.HealthDepleted += _ => depleted = true;
var damage = new DamageRecord(Vector3.Zero, new RDamage(100.0f, EDamageTypes.Normal));
var record = cHealth.ReduceHealth(source: null!, damageRecord: damage);
AssertBool(depleted).IsTrue();
AssertFloat(cHealth.CurrentHealth).IsEqual(0.0f);
AssertFloat(record.CurrentHealth).IsEqual(-50.0f);
AssertFloat(record.MaxHealth).IsEqual(50.0f);
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.interfaces;
using Movementtests.systems.damage;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class KnockbackComponentUnitTest
{
[TestCase]
public void RegisterAndComputeKnockback()
{
var cKnock = new CKnockback();
cKnock.RKnockback = new RKnockback(2.0f);
cKnock.GlobalPosition = Vector3.Zero;
var damage = new DamageRecord(new Vector3(10, 0, 0), new RDamage(0, EDamageTypes.Normal));
var record = new KnockbackRecord(damage, 1.5f);
cKnock.RegisterKnockback(record);
var force = cKnock.ComputeKnockback();
// Direction from source(10,0,0) to target(0,0,0) is (-1,0,0), scaled by modifier(2) and multiplier(1.5) => (-3,0,0)
AssertVector(force).IsEqual(new Vector3(-3, 0, 0));
// Second call returns zero since internal state resets
var second = cKnock.ComputeKnockback();
AssertVector(second).IsEqual(Vector3.Zero);
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.interfaces;
using Movementtests.scenes.movement;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class MovementSystemUnitTest
{
[TestCase]
public void GroundedMovementAcceleratesAndAppliesGravity()
{
var move = new CGroundedMovement();
move.RMovement = new RMovement(speed: 10.0f, acceleration: 1.0f, gravityModifier: 0.5f, targetHeight: 0.0f);
move.WallInFrontRayCast = new RayCast3D();
//move.GlobalPosition = Vector3.Zero;
var inputs = new MovementInputs(
Velocity: Vector3.Zero,
TargetLocation: new Vector3(10, 0, 0),
isOnFloor: false,
gravity: Vector3.Down * 9.8f,
delta: 1.0
);
var v = move.ComputeVelocity(inputs);
AssertVector(v).IsEqualApprox(new Vector3(10, -4.9f, 0), new Vector3(0.001f, 0.001f, 0.001f));
}
}

View File

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

View File

@@ -0,0 +1,30 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.interfaces;
using Movementtests.systems.damage;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class EnemyUnitTest
{
[TestCase]
public void ComputeDamageNoComponent()
{
var enemy = new Enemy();
var input = new DamageRecord(Vector3.Zero, new RDamage(10.0f, EDamageTypes.Normal));
var result = enemy.ComputeDamage(input);
AssertFloat(result.Damage.DamageDealt).IsEqual(0.0f);
}
[TestCase]
public void Unstun()
{
var enemy = new Enemy();
enemy.IsStunned = true;
enemy.Unstun();
AssertBool(enemy.IsStunned).IsFalse();
}
}

View File

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

View File

@@ -0,0 +1,55 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.systems;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class DashSystemUnitTest
{
private DashSystem _dashSystem;
[BeforeTest]
public void SetupTest()
{
_dashSystem = new DashSystem();
_dashSystem.DashCast3D = new ShapeCast3D();
_dashSystem.AddChild(_dashSystem.DashCast3D);
_dashSystem.DashCastDrop = new ShapeCast3D();
_dashSystem.AddChild(_dashSystem.DashCastDrop);
_dashSystem.DashTarget = new MeshInstance3D();
_dashSystem.AddChild(_dashSystem.DashTarget);
_dashSystem.DashDropIndicator = new MeshInstance3D();
_dashSystem.AddChild(_dashSystem.DashDropIndicator);
_dashSystem.DashDropLocationIndicator = new MeshInstance3D();
_dashSystem.AddChild(_dashSystem.DashDropLocationIndicator);
}
[AfterTest]
public void CleanupTest()
{
_dashSystem?.Free();
}
[TestCase]
public void TestStopPreparingDash()
{
_dashSystem.CanDashThroughTarget = true;
_dashSystem.DashTarget.Visible = true;
_dashSystem.DashDropIndicator.Visible = true;
_dashSystem.DashDropLocationIndicator.Visible = true;
_dashSystem.StopPreparingDash();
AssertBool(_dashSystem.CanDashThroughTarget).IsFalse();
AssertBool(_dashSystem.DashTarget.Visible).IsFalse();
AssertBool(_dashSystem.DashDropIndicator.Visible).IsFalse();
AssertBool(_dashSystem.DashDropLocationIndicator.Visible).IsFalse();
}
}

View File

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

View File

@@ -0,0 +1,94 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.systems;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class HeadSystemUnitTest
{
private HeadSystem _head;
[BeforeTest]
public void SetupTest()
{
_head = new HeadSystem();
_head.Camera = new Camera3D();
_head.AddChild(_head.Camera);
// _head._cameraAnchor = new Marker3D();
_head.AddChild(_head.CameraAnchor);
_head.FpRig = new Node3D();
_head.AddChild(_head.FpRig);
_head.CameraAnchor = new Node3D();
_head.AddChild(_head.CameraAnchor);
_head.RightHandedWeapon = new Node3D();
_head.AddChild(_head.RightHandedWeapon);
_head.LeftHandedWeapon = new Node3D();
_head.AddChild(_head.LeftHandedWeapon);
}
[AfterTest]
public void CleanupTest()
{
_head?.Free();
}
[TestCase]
public void TestResetHeadBobbing()
{
_head._bobbingAccumulator = 10.0f;
_head.ResetHeadBobbing();
AssertFloat(_head._bobbingAccumulator).IsEqual(0.0f);
}
[TestCase]
public void TestComputeHowMuchInputForward()
{
Vector3 forwardInput = new Vector3(0, 0, -1);
AssertFloat(_head.ComputeHowMuchInputForward(forwardInput)).IsEqual(1.0f);
Vector3 backwardInput = new Vector3(0, 0, 1);
AssertFloat(_head.ComputeHowMuchInputForward(backwardInput)).IsEqual(-1.0f);
}
[TestCase]
public void TestComputeHowMuchInputSideways()
{
Vector3 rightInput = new Vector3(1, 0, 0);
AssertFloat(_head.ComputeHowMuchInputSideways(rightInput)).IsEqual(1.0f);
Vector3 leftInput = new Vector3(-1, 0, 0);
AssertFloat(_head.ComputeHowMuchInputSideways(leftInput)).IsEqual(-1.0f);
}
[TestCase]
public void TestGetForwardHorizontalVector()
{
Vector3 forward = _head.GetForwardHorizontalVector();
AssertVector(forward).IsEqualApprox(Vector3.Back, new Vector3(0.001f, 0.001f, 0.001f));
}
[TestCase]
public void TestLookAroundRotation()
{
var inputs = new HeadSystem.CameraParameters(
Delta: 0.016,
LookDir: new Vector2(1, 0),
PlayerInput: Vector3.Zero,
PlayerVelocity: Vector3.Zero,
WallContactPoint: Vector3.Zero,
SensitivitMultiplier: 1.0f,
WithCameraJitter: false,
WithCameraBobbing: false,
BobbingMultiplier: 1.0f,
FovMultiplier: 1.0f
);
float initialY = _head.Rotation.Y;
_head.LookAround(inputs);
AssertFloat(_head.Rotation.Y).IsEqual(initialY + 1.0f);
}
}

View File

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

View File

@@ -0,0 +1,149 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.interfaces;
using Movementtests.systems.damage;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class PlayerControllerUnitTest
{
private PlayerController _player;
[BeforeTest]
public void SetupTest()
{
_player = new PlayerController();
_player.TargetSpeed = 7.0f;
_player.Gravity = 9.8f;
var rHealth = new RHealth(100.0f);
_player.RHealth = rHealth;
_player.CHealth = new CHealth { RHealth = rHealth, CurrentHealth = 100.0f };
}
[AfterTest]
public void CleanupTest()
{
_player?.Free();
}
[TestCase]
public void TestCalculateGravityForce()
{
_player.Weight = 3.0f;
// gravity is 9.8f
AssertFloat(_player.CalculateGravityForce()).IsEqualApprox(29.4f, 0.001f);
}
[TestCase]
public void TestIsPlayerInputtingForward()
{
// Test Keyboard Input
_player.InputDeviceChanged(false);
_player.OnInputMoveKeyboard(Vector3.Forward);
AssertBool(_player.IsPlayerInputtingForward()).IsTrue();
_player.OnInputMoveKeyboard(Vector3.Back);
AssertBool(_player.IsPlayerInputtingForward()).IsFalse();
// Test Gamepad Input
_player.InputDeviceChanged(true);
_player.OnInputMove(new Vector3(0, 0, -1));
AssertBool(_player.IsPlayerInputtingForward()).IsTrue();
}
[TestCase]
public void TestSetVerticalVelocity()
{
_player.Velocity = new Vector3(1, 0, 2);
_player.SetVerticalVelocity(5.0f);
AssertVector(_player.Velocity).IsEqual(new Vector3(1, 5, 2));
}
[TestCase]
public void TestComputeHVelocityGround()
{
_player.Velocity = Vector3.Zero;
_player.AccelerationFloor = 10.0f;
float delta = 0.1f;
Vector3 newVelocity = _player.ComputeHVelocity(delta, _player.AccelerationFloor, _player.DecelerationFloor, Vector3.Forward);
AssertVector(newVelocity).IsEqual(new Vector3(0, 0, -7.0f));
}
[TestCase]
public void TestComputeHVelocityAir()
{
_player.Velocity = new Vector3(5, 0, 0);
_player.AccelerationAir = 2.0f;
_player.DecelerationAir = 2.0f;
float delta = 0.5f;
Vector3 newVelocity = _player.ComputeHVelocity(delta, _player.AccelerationAir, _player.DecelerationAir, Vector3.Zero);
AssertVector(newVelocity).IsEqual(Vector3.Zero);
}
[TestCase]
public void TestReduceHealth()
{
var damageRecord = new DamageRecord(Vector3.Zero, new RDamage(25.0f, EDamageTypes.Normal));
_player.ReduceHealth(_player, damageRecord);
AssertFloat(_player.CHealth.CurrentHealth).IsEqual(75.0f);
}
[TestCase]
public void TestEmpoweredActionsLeft()
{
var mockUi = new PlayerUi();
var dashIcons = new TextureRect[3] { new TextureRect(), new TextureRect(), new TextureRect() };
mockUi.DashIcons = dashIcons;
_player.PlayerUi = mockUi;
_player.EmpoweredActionsLeft = 2;
AssertInt(_player.EmpoweredActionsLeft).IsEqual(2);
AssertBool(dashIcons[0].Visible).IsTrue();
AssertBool(dashIcons[1].Visible).IsTrue();
AssertBool(dashIcons[2].Visible).IsFalse();
}
[TestCase]
public void TestDashCooldownTimeout()
{
_player.CanDash = false;
_player.DashCooldownTimeout();
AssertBool(_player.CanDash).IsTrue();
}
[TestCase]
public void TestGetInputLocalHDirection()
{
_player.InputDeviceChanged(false);
_player.OnInputMoveKeyboard(new Vector3(1, 0, 1));
Vector3 expected = new Vector3(1, 0, 1).Normalized();
AssertVector(_player.GetInputLocalHDirection()).IsEqualApprox(expected, new Vector3(0.001f, 0.001f, 0.001f));
}
[TestCase]
public void TestComputeKnockback()
{
var cKnockback = new CKnockback();
cKnockback.RKnockback = new RKnockback(10.0f);
_player.CKnockback = cKnockback;
var damageRecord = new DamageRecord(new Vector3(10, 0, 0), new RDamage(0, EDamageTypes.Normal));
var knockbackRecord = new KnockbackRecord(damageRecord, 1.0f);
_player.GlobalPosition = Vector3.Zero;
cKnockback.GlobalPosition = Vector3.Zero;
_player.RegisterKnockback(knockbackRecord);
Vector3 knockback = cKnockback.ComputeKnockback();
AssertVector(knockback).IsEqual(new Vector3(-10, 0, 0));
}
}

View File

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

View File

@@ -0,0 +1,51 @@
using Godot;
using GdUnit4;
using static GdUnit4.Assertions;
using Movementtests.systems;
using Movementtests.systems.damage;
namespace Movementtests.tests;
[TestSuite, RequireGodotRuntime]
public class WeaponSystemUnitTest
{
private WeaponSystem _weapon;
[BeforeTest]
public void SetupTest()
{
_weapon = new WeaponSystem();
_weapon.RDamage = new RDamage(5.0f, EDamageTypes.Normal);
_weapon.WeaponMesh = new MeshInstance3D();
_weapon.AddChild(_weapon.WeaponMesh);
_weapon.WeaponLocationIndicator = new MeshInstance3D();
_weapon.AddChild(_weapon.WeaponLocationIndicator);
}
[AfterTest]
public void CleanupTest()
{
_weapon?.Free();
}
[TestCase]
public void TestWeaponLeftAndBackVisibility()
{
_weapon.Visible = false;
_weapon.WeaponLeft();
AssertBool(_weapon.Visible).IsTrue();
_weapon.WeaponBack();
AssertBool(_weapon.Visible).IsFalse();
}
[TestCase]
public void TestThrowWeaponOnCurveSetsUnfrozen()
{
_weapon.Freeze = true;
_weapon.ThrowWeaponOnCurve();
AssertBool(_weapon.Freeze).IsFalse();
}
}

View File

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

View File

@@ -0,0 +1,50 @@
using System.Threading.Tasks;
using Godot;
using GodotStateCharts;
namespace Movementtests.tests;
using GdUnit4;
using static GdUnit4.Assertions;
[TestSuite, RequireGodotRuntime]
public class PlayerInteractionsTest
{
private ISceneRunner _runner;
private Node _scene;
private PlayerController _player;
private readonly float _tolerance = 0.01f;
private readonly Vector3 _vectorTolerance = new Vector3(0.01f, 0.01f, 0.01f);
[BeforeTest]
public void SetupTest()
{
_runner = ISceneRunner.Load("res://tests/player/interactions/player_interactions_scene.tscn");
_scene = _runner.Scene()!;
var player = _scene.FindChild("Player") as PlayerController;
_player = player!;
}
[AfterTest]
public void CleanupTest() {}
[TestCase("BaseLocation")]
public async Task PlayerMoveForward(string markerName)
{
var marker = _scene.FindChild(markerName) as Marker3D;
AssertObject(marker).IsNotNull();
_player.GlobalPosition = marker!.GlobalPosition;
await _runner.AwaitIdleFrame();
var startPos = _player.GlobalPosition;
_runner.SimulateKeyPress(Key.W);
await _runner.AwaitMillis(300);
_runner.SimulateKeyRelease(Key.W);
var endPos = _player.GlobalPosition;
var direction = startPos.DirectionTo(endPos);
AssertVector(direction).IsEqualApprox(Vector3.Forward, _vectorTolerance);
}
}

View File

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

View File

@@ -0,0 +1,35 @@
[gd_scene format=3 uid="uid://l0lflvsjbyvs"]
[ext_resource type="Material" uid="uid://31aulub2nqov" path="res://assets/materials/greybox/m_greybox.tres" id="1_dv0re"]
[ext_resource type="PackedScene" uid="uid://bei4nhkf8lwdo" path="res://scenes/player_controller/PlayerController.tscn" id="2_52d52"]
[node name="PlayerMovementScene" type="Node3D" unique_id=231040688]
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=241909240]
use_collision = true
collision_layer = 256
collision_mask = 65553
[node name="Ground" type="CSGBox3D" parent="CSGCombiner3D" unique_id=432200143]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, -0.5, -3.25)
use_collision = true
collision_layer = 256
collision_mask = 65553
size = Vector3(1000, 1, 1000)
material = ExtResource("1_dv0re")
[node name="Ground2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=854660236]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.75, 0.5, -1.75)
use_collision = true
collision_layer = 256
collision_mask = 65553
size = Vector3(1.5, 1, 1.5)
material = ExtResource("1_dv0re")
[node name="Player" parent="." unique_id=709076448 instance=ExtResource("2_52d52")]
TutorialDone = true
[node name="BaseLocation" type="Marker3D" parent="." unique_id=1793710692]
[node name="MantleLocation1" type="Marker3D" parent="." unique_id=550080845]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.75, 0, 0)

View File

@@ -0,0 +1,109 @@
using System.Threading.Tasks;
using Godot;
using GodotStateCharts;
namespace Movementtests.tests;
using GdUnit4;
using static GdUnit4.Assertions;
[TestSuite, RequireGodotRuntime]
public class PlayerMovementTest
{
private ISceneRunner _runner;
private Node _scene;
private PlayerController _player;
private readonly float _tolerance = 0.01f;
private readonly Vector3 _vectorTolerance = new Vector3(0.01f, 0.01f, 0.01f);
[Before]
public void Setup() {}
[After]
public void Cleanup() {}
[BeforeTest]
public void SetupTest()
{
_runner = ISceneRunner.Load("res://tests/player/movement/player_movement_scene.tscn");
_scene = _runner.Scene()!;
var player = _scene.FindChild("Player") as PlayerController;
_player = player!;
}
[AfterTest]
public void CleanupTest() {}
[TestCase("BaseLocation")]
public async Task PlayerMoveForward(string markerName)
{
var marker = _scene.FindChild(markerName) as Marker3D;
AssertObject(marker).IsNotNull();
_player.GlobalPosition = marker!.GlobalPosition;
await _runner.AwaitIdleFrame();
var startPos = _player.GlobalPosition;
_runner.SimulateKeyPress(Key.W);
await _runner.AwaitMillis(100);
_runner.SimulateKeyRelease(Key.W);
var endPos = _player.GlobalPosition;
var direction = startPos.DirectionTo(endPos);
AssertVector(direction).IsEqualApprox(Vector3.Forward, _vectorTolerance);
}
[TestCase("BaseLocation")]
public async Task PlayerJump(string markerName)
{
var marker = _scene.FindChild(markerName) as Marker3D;
AssertObject(marker).IsNotNull();
_player.GlobalPosition = marker!.GlobalPosition;
await _runner.AwaitIdleFrame();
var startPos = _player.GlobalPosition;
_runner.SimulateKeyPress(Key.Space);
await _runner.AwaitIdleFrame();
var jumping = StateChartState.Of(_player.GetNode("StateChart/Root/Movement/Jump"));
AssertBool(jumping.Active).IsTrue();
_runner.SimulateKeyRelease(Key.Space);
await _runner.AwaitIdleFrame();
var endPos = _player.GlobalPosition;
var direction = startPos.DirectionTo(endPos);
AssertVector(direction).IsEqualApprox(Vector3.Up, _vectorTolerance);
AssertVector(_player.Velocity.Normalized()).IsEqualApprox(Vector3.Up, _vectorTolerance);
await _runner.AwaitMillis(600);
endPos = _player.GlobalPosition;
AssertVector(endPos - startPos).IsEqualApprox(Vector3.Zero, _vectorTolerance);
var grounded = StateChartState.Of(_player.GetNode("StateChart/Root/Movement/Grounded"));
AssertBool(grounded.Active).IsTrue();
}
[TestCase("MantleLocation1")]
public async Task PlayerMantle(string markerName)
{
var marker = _scene.FindChild(markerName) as Marker3D;
AssertObject(marker).IsNotNull();
_player.GlobalPosition = marker!.GlobalPosition;
await _runner.AwaitMillis(100);
var startPos = _player.GlobalPosition;
_runner.SimulateKeyPress(Key.Space);
await _runner.AwaitMillis(100);
var mantling = StateChartState.Of(_player.GetNode("StateChart/Root/Movement/Mantling"));
AssertBool(mantling.Active).IsTrue();
_runner.SimulateKeyRelease(Key.Space);
await _runner.AwaitMillis(500);
var endPos = _player.GlobalPosition;
AssertFloat((endPos - startPos).Length()).IsGreater(_tolerance);
AssertFloat(endPos.Y).IsEqualApprox(1.0f, _tolerance);
}
}

View File

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

View File

@@ -0,0 +1,35 @@
[gd_scene format=3 uid="uid://i8kb38q7bdfk"]
[ext_resource type="Material" uid="uid://31aulub2nqov" path="res://assets/materials/greybox/m_greybox.tres" id="1_bdfhg"]
[ext_resource type="PackedScene" uid="uid://bei4nhkf8lwdo" path="res://scenes/player_controller/PlayerController.tscn" id="1_hg1sy"]
[node name="PlayerMovementScene" type="Node3D" unique_id=231040688]
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=241909240]
use_collision = true
collision_layer = 256
collision_mask = 65553
[node name="Ground" type="CSGBox3D" parent="CSGCombiner3D" unique_id=432200143]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, -0.5, -3.25)
use_collision = true
collision_layer = 256
collision_mask = 65553
size = Vector3(1000, 1, 1000)
material = ExtResource("1_bdfhg")
[node name="Ground2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=854660236]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.75, 0.5, -1.75)
use_collision = true
collision_layer = 256
collision_mask = 65553
size = Vector3(1.5, 1, 1.5)
material = ExtResource("1_bdfhg")
[node name="Player" parent="." unique_id=709076448 instance=ExtResource("1_hg1sy")]
TutorialDone = true
[node name="BaseLocation" type="Marker3D" parent="." unique_id=1793710692]
[node name="MantleLocation1" type="Marker3D" parent="." unique_id=550080845]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.75, 0, 0)

View File

@@ -1,27 +0,0 @@
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Tags;
using Godot;
namespace Movementtests.tools;
public partial class ForgeManager : Node
{
public CuesManager CuesManager { get; private set; } = new CuesManager();
public TagsManager TagsManager { get; private set; } = new TagsManager(
[
"character.player",
"class.warrior",
"status.stunned",
"status.burning",
"status.enraged",
"status.immune.fire",
"cues.damage.fire",
"events.combat.damage",
"events.combat.hit",
"cooldown.fireball"
]);
public ForgeManager()
{
}
}

View File

@@ -1,3 +0,0 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json"
}