Files
MovementTests/scenes/player_controller/components/weapon/WeaponSystem.cs
Minimata 42ff38f39b
Some checks failed
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) Failing after 4m23s
yow it's working or wat
2026-03-29 17:30:14 +02:00

434 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Abilities;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Effects.Calculator;
using Gamesmiths.Forge.Effects.Components;
using Gamesmiths.Forge.Effects.Duration;
using Gamesmiths.Forge.Effects.Magnitudes;
using Gamesmiths.Forge.Effects.Periodic;
using Gamesmiths.Forge.Events;
using Gamesmiths.Forge.Tags;
using Godot;
using GodotStateCharts;
using Movementtests.addons.godot_state_charts.csharp;
using Movementtests.forge.abilities;
using Movementtests.interfaces;
using Movementtests.scenes.player_controller.components.weapon;
using Movementtests.systems.damage;
using Movementtests.tools;
using Movementtests.tools.calculators;
namespace Movementtests.systems;
public record struct WeaponEventPayload(String Message);
public class ClosureBehavior<TPayload>(
Action<AbilityBehaviorContext, TPayload> callback) : IAbilityBehavior<TPayload>
{
public void OnStarted(AbilityBehaviorContext context, TPayload data)
{
callback(context, data);
context.InstanceHandle.End();
}
public void OnEnded(AbilityBehaviorContext context){}
}
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_sword.png")]
public partial class WeaponSystem : RigidBody3D, IDamageDealer, IForgeEntity
{
[Signal]
public delegate void WeaponThrownEventHandler();
[Signal]
public delegate void WeaponRetrievedEventHandler();
[Export]
public RDamage RDamage { get; set; }
[Export(PropertyHint.Range, "0,100,1,or_greater")]
public float ThrowForce { get; set; } = 1f;
[Export(PropertyHint.Range, "0,0.2,0.01,or_greater")]
public float StraightThrowDuration { get; set; } = 0.1f;
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 Transition _handToFlying = null!;
private Transition _flyingToHand = null!;
private Transition _plantedToHand = null!;
private Transition _plantedToFlying = null!;
private Transition _toPlanted = null!;
private ShapeCast3D _dashCast3D = null!;
public Timer WeaponFlyingTick = null!;
private Transform3D _startTransform;
private Vector3 _startMeshRotation;
private Vector3 _throwDirection;
public Vector3 PlantLocation { get; set; }
public Vector3 PlantNormal { get; set; }
public Node? PlantObject { get; set; }
public MeshInstance3D WeaponLocationIndicator { get; set; } = null!;
public StandardMaterial3D WeaponLocationIndicatorMaterial { get; set; } = null!;
public MeshInstance3D WeaponMesh { get; set; } = null!;
public Tag WeaponFlyingTickEventTag;
public Tag WeaponStartedFlyingEventTag;
public Tag WeaponStoppedFlyingEventTag;
public Tag WeaponHandToFlyingEventTag;
public Tag WeaponFlyingToHandEventTag;
public Tag WeaponPlantedToHandEventTag;
public Tag WeaponPlantedToFlyingEventTag;
public Tag WeaponPlantedEventTag;
public Tag WeaponInHandStatusTag;
public Tag WeaponFlyingStatusTag;
public Tag WeaponPlantedStatusTag;
public Tag WeaponFlyingAbilityTag;
private RAbilityBase? _flyingAbility;
public List<RAbilityBase> AbilityLoadout { get; } = [];
public void Init()
{
_weaponState = StateChart.Of(GetNode("StateChart"));
InHandState = StateChartState.Of(GetNode("StateChart/Root/InHand"));
FlyingState = StateChartState.Of(GetNode("StateChart/Root/Flying"));
PlantedState = StateChartState.Of(GetNode("StateChart/Root/Planted"));
_handToFlying = Transition.Of(GetNode("StateChart/Root/InHand/ToFlying"));
_flyingToHand = Transition.Of(GetNode("StateChart/Root/Flying/ToHand"));
_plantedToHand = Transition.Of(GetNode("StateChart/Root/Planted/ToHand"));
_plantedToFlying = Transition.Of(GetNode("StateChart/Root/Planted/ToFlying"));
_toPlanted = Transition.Of(GetNode("StateChart/Root/ToPlanted"));
WeaponLocationIndicator = GetNode<MeshInstance3D>("WeaponLocationIndicator");
WeaponLocationIndicator.Visible = false;
WeaponLocationIndicatorMaterial = (WeaponLocationIndicator.GetActiveMaterial(0) as StandardMaterial3D)!;
WeaponFlyingTick = GetNode<Timer>("WeaponFlyingTick");
WeaponMesh = GetNode<MeshInstance3D>("Weapon");
_startMeshRotation = WeaponMesh.Rotation;
_startTransform = Transform;
Freeze = true;
Visible = false;
// Forge
var tagsManager = ForgeManager.GetTagsManager(this);
var cuesManager = ForgeManager.GetCuesManager(this);
WeaponFlyingTickEventTag = Tag.RequestTag(tagsManager, "events.weapon.flyingTick");
WeaponStartedFlyingEventTag = Tag.RequestTag(tagsManager, "events.weapon.startedFlying");
WeaponStoppedFlyingEventTag = Tag.RequestTag(tagsManager, "events.weapon.stoppedFlying");
WeaponHandToFlyingEventTag = Tag.RequestTag(tagsManager, "events.weapon.handToFlying");
WeaponFlyingToHandEventTag = Tag.RequestTag(tagsManager, "events.weapon.flyingToHand");
WeaponPlantedToHandEventTag = Tag.RequestTag(tagsManager, "events.weapon.plantedToHand");
WeaponPlantedToFlyingEventTag = Tag.RequestTag(tagsManager, "events.weapon.plantedToFlying");
WeaponPlantedEventTag = Tag.RequestTag(tagsManager, "events.weapon.planted");
WeaponInHandStatusTag = Tag.RequestTag(tagsManager, "status.weapon.inHand");
WeaponFlyingStatusTag = Tag.RequestTag(tagsManager, "status.weapon.flying");
WeaponPlantedStatusTag = Tag.RequestTag(tagsManager, "status.weapon.planted");
WeaponFlyingAbilityTag = Tag.RequestTag(tagsManager,"abilities.weapon.flying");
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();
CreateFlyingAbility();
BodyEntered += OnThrownWeaponReachesGround;
InHandState.StateExited += WeaponLeft;
InHandState.StateEntered += WeaponBack;
_handToFlying.Taken += () =>
{
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponHandToFlyingEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "handToFlying")
});
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponStartedFlyingEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "startedFlying")
});
};
_flyingToHand.Taken += () =>
{
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponFlyingToHandEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "flyingToHand")
});
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponStoppedFlyingEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "stoppedFlying")
});
};
_plantedToHand.Taken += () =>
{
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponPlantedToHandEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "plantedToHand")
});
};
_plantedToFlying.Taken += () =>
{
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponPlantedToFlyingEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "plantedToFlying")
});
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponStartedFlyingEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "startedFlying")
});
};
_toPlanted.Taken += () =>
{
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponPlantedEventTag.GetSingleTagContainer()!,
Source = this,
Target = _plantedEntity,
Payload = new WeaponEventPayload(Message: "planted")
});
Events.Raise(new EventData<WeaponEventPayload>
{
EventTags = WeaponStoppedFlyingEventTag.GetSingleTagContainer()!,
Source = this,
Payload = new WeaponEventPayload(Message: "stoppedFlying")
});
};
Events.Subscribe<WeaponEventPayload>(WeaponStoppedFlyingEventTag, data =>
{
GD.Print("This is removing the periodic effect to the flying weapon");
GD.Print(data.Payload.Message);
if (_flyingWeaponEffectHandle is { IsValid: true }) EffectsManager.RemoveEffect(_flyingWeaponEffectHandle);
});
}
private ActiveEffectHandle? _flyingWeaponEffectHandle;
public void CreateFlyingAbility()
{
var flyingWeaponEffectData = new EffectData(
"Flying Weapon Effect Data",
new DurationData(DurationType.Infinite),
customExecutions:
[
new FlyingWeaponExecution(this)
],
periodicData: new PeriodicData(new ScalableFloat(0.2f), false, PeriodInhibitionRemovedPolicy.ResetPeriod)
);
var weaponHandToFlyingAbilityData = new AbilityData(
name: "WeaponHandToFlyingSword",
abilityTags: WeaponFlyingAbilityTag.GetSingleTagContainer(),
instancingPolicy: AbilityInstancingPolicy.PerEntity,
abilityTriggerData: AbilityTriggerData.ForEvent<WeaponEventPayload>(WeaponStartedFlyingEventTag),
behaviorFactory: () => new ClosureBehavior<WeaponEventPayload>((ctx, payload) =>
{
GD.Print("This is applying the periodic effect to the flying weapon");
GD.Print(payload.Message);
_flyingWeaponEffectHandle = EffectsManager.ApplyEffect(new Effect(flyingWeaponEffectData, new EffectOwnership(this, this)));
}));
Abilities.GrantAbilityPermanently(weaponHandToFlyingAbilityData, 1, LevelComparison.None, this);
}
public void GrantNewAbilityForWeaponFly(RExplodingSword ability)
{
var weaponFlyingAbilityData = new AbilityData(
name: "WeaponFlyingSwordAbility",
abilityTags: WeaponFlyingAbilityTag.GetSingleTagContainer(),
instancingPolicy: AbilityInstancingPolicy.PerEntity,
abilityTriggerData: AbilityTriggerData.ForEvent<WeaponEventPayload>(WeaponFlyingTickEventTag),
behaviorFactory: () => ability.Behavior(new ExplodingSwordCreation(this)));
var weaponFlyGrantAbilityConfig = new GrantAbilityConfig(
weaponFlyingAbilityData,
ScalableLevel: new ScalableInt(1),
RemovalPolicy: AbilityDeactivationPolicy.CancelImmediately,
InhibitionPolicy: AbilityDeactivationPolicy.CancelImmediately,
TryActivateOnGrant: false,
TryActivateOnEnable: false,
LevelOverridePolicy: LevelComparison.Higher);
var weaponFlyGrantComponent = new GrantAbilityEffectComponent([weaponFlyGrantAbilityConfig]);
var weaponFlyGrantEffect = new EffectData(
"Grant Weapon Fly Ability",
new DurationData(DurationType.Infinite),
effectComponents: [weaponFlyGrantComponent]);
EffectsManager.ApplyEffect(new Effect(weaponFlyGrantEffect, new EffectOwnership(this, this)));
GD.Print("New weapon flight ability granted");
}
public void WeaponLeft()
{
Visible = true;
// WeaponLocationIndicator.Visible = true;
EmitSignalWeaponThrown();
}
public void WeaponBack()
{
Visible = false;
// WeaponLocationIndicator.Visible = false;
EmitSignalWeaponRetrieved();
}
public void PlaceWeaponForTutorial(Vector3 location)
{
_weaponState.SendEvent("plant");
Freeze = true;
GlobalPosition = location;
PlantLocation = location;
}
public void ThrowWeapon(Vector3 end, bool hasHit, Vector3 collisionLocation, Vector3 collisionNormal, Node collidedObject)
{
_weaponState.SendEvent("throw");
_throwDirection = (end - GlobalPosition).Normalized();
PlantLocation = collisionLocation;
PlantNormal = collisionNormal;
LookAt(end);
var tween = GetTree().CreateTween();
tween.TweenProperty(this, "global_position", end, StraightThrowDuration);
if (hasHit)
{
PlantObject = collidedObject;
tween.Finished += PlantWeaponInWall;
}
else
tween.Finished += ThrowWeaponOnCurve;
}
private IForgeEntity? _plantedEntity;
public void PlantInEnemy(Node3D enemy)
{
GetTree().GetRoot().CallDeferred(Node.MethodName.RemoveChild, this);
enemy.CallDeferred(Node.MethodName.AddChild, this);
if (enemy is IForgeEntity victim) _plantedEntity = victim;
else _plantedEntity = null;
if (enemy is IDamageable damageable)
{
damageable.TakeDamage(new DamageRecord(GlobalPosition, RDamage));
}
}
public void RethrowWeapon()
{
_weaponState.SendEvent("throw");
_throwDirection = Vector3.Up;
ThrowWeaponOnCurve();
}
public void ThrowWeaponOnCurve()
{
Freeze = false;
ApplyImpulse(_throwDirection * ThrowForce);
}
public void PlantWeaponInWall()
{
Freeze = true;
WeaponMesh.Rotation = _startMeshRotation;
// WeaponLocationIndicatorMaterial.StencilColor = new Color(1f, 0.2f, 0.2f);
if (PlantObject is Node3D node)
PlantInEnemy(node);
_weaponState.SendEvent("plant");
CallDeferred(Node3D.MethodName.SetGlobalPosition, PlantLocation);
CallDeferred(Node3D.MethodName.LookAt, GlobalTransform.Origin + PlantNormal, Vector3.Up, true);
}
public void OnThrownWeaponReachesGround(Node other)
{
PlantObject = other;
PlantWeaponInWall();
}
public void ResetWeapon()
{
_weaponState.SendEvent("recover");
Transform = _startTransform;
Freeze = true;
Visible = false;
}
public override void _IntegrateForces(PhysicsDirectBodyState3D state)
{
base._IntegrateForces(state);
if (!Freeze && state.GetContactCount() > 0)
{
PlantLocation = state.GetContactLocalPosition(0);
PlantNormal = state.GetContactLocalNormal(0);
}
}
public override void _Process(double delta)
{
if (!FlyingState.Active) return;
WeaponMesh.Rotation = new Vector3(WeaponMesh.Rotation.X, WeaponMesh.Rotation.Y + (float) delta * 100, WeaponMesh.Rotation.Z);
//LookAt(GlobalTransform.Origin + LinearVelocity.Normalized(), Vector3.Up, false);
}
public bool IsPlantedUnderPlatform()
{
return PlantedState.Active && GlobalRotation.X > 1 && Math.Abs(GlobalRotation.Y) > 1;
}
public bool IsPlantedInWall()
{
return PlantedState.Active && Math.Abs(GlobalRotation.X) + Math.Abs(GlobalRotation.Z) < 0.3;
}
}