434 lines
16 KiB
C#
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;
|
|
}
|
|
} |