using System; using System.Collections.Generic; using Chickensoft.AutoInject; using Chickensoft.Introspection; using Gamesmiths.Forge.Abilities; using Gamesmiths.Forge.Attributes; using Gamesmiths.Forge.Core; using Gamesmiths.Forge.Cues; 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.Godot.Core; using Gamesmiths.Forge.Godot.Nodes; using Gamesmiths.Forge.Godot.Resources; using Gamesmiths.Forge.Godot.Resources.Abilities; using Gamesmiths.Forge.Statescript; using Gamesmiths.Forge.Tags; using Godot; using GodotStateCharts; using Movementtests.addons.godot_state_charts.csharp; using Movementtests.interfaces; using Movementtests.tools; using Node = Godot.Node; namespace Movementtests.systems; [GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_sword.png"), Meta(typeof(IAutoNode))] public partial class WeaponSystem : RigidBody3D, IDamageDealer, IForgeEntity { public override void _Notification(int what) => this.Notify(what); #region Dependencies [Dependency] public TagsManager TagsManager => this.DependOn(); [Dependency] public CuesManager CuesManager => this.DependOn(); #endregion #region Enums public enum WeaponEvent { StartedFlying, StoppedFlying, FlyingTick } #endregion #region Signals [Signal] public delegate void WeaponThrownEventHandler(); [Signal] public delegate void WeaponRetrievedEventHandler(); #endregion #region Forge private Dictionary _grantedWeaponStartedFlyingAbilities = new (); private Dictionary _grantedWeaponStoppedFlyingAbilities = new (); private Dictionary _grantedWeaponFlyingTickAbilities = new (); 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 AbilityHandle? _weaponFlyingAbility; #endregion #region Inspector [Export] public ForgeAbilityData? FlyingTickAbility { get; set; } [Export] public RDamage RDamage { get; set; } [Export(PropertyHint.Range, "0,2,0.01,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; #endregion #region IForgeEntity // Perfectly forward the IForgeEntity interface to the ForgeEntity component public EntityAttributes Attributes { get => ForgeEntity.Attributes; set => ForgeEntity.Attributes = value; } public EntityTags Tags { get => ForgeEntity.Tags; set => ForgeEntity.Tags = value; } public EffectsManager EffectsManager { get => ForgeEntity.EffectsManager; set => ForgeEntity.EffectsManager = value; } public EntityAbilities Abilities { get => ForgeEntity.Abilities; set => ForgeEntity.Abilities = value; } public EventManager Events { get => ForgeEntity.Events; set => ForgeEntity.Events = value; } public Variables SharedVariables { get => ForgeEntity.SharedVariables; set => ForgeEntity.SharedVariables = value; } #endregion #region Publics public StateChartState InHandState = null!; public StateChartState FlyingState = null!; public StateChartState PlantedState = null!; public Vector3 PlantLocation { get; set; } public Vector3 PlantNormal { get; set; } public Node? PlantObject { get; set; } public StandardMaterial3D WeaponLocationIndicatorMaterial { get; set; } = null!; [Node("ForgeEntityNode")] public required ForgeEntityNode ForgeEntity { get; set;} [Node("Weapon")] public required MeshInstance3D WeaponMesh { get; set; } [Node("WeaponLocationIndicator")] public required MeshInstance3D WeaponLocationIndicator { get; set; } #endregion #region Privates private StateChart _weaponState = 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!; private Transform3D _startTransform; private Vector3 _startMeshRotation; private Vector3 _throwDirection; #endregion public void OnReady() { #region StateManagement _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")); #endregion // Initial setup _startMeshRotation = WeaponMesh.Rotation; _startTransform = Transform; Freeze = true; Visible = false; WeaponLocationIndicator.Visible = false; WeaponLocationIndicatorMaterial = (WeaponLocationIndicator.GetActiveMaterial(0) as StandardMaterial3D)!; } public void OnResolved() { // Forge 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"); // Manage weapon flying tick raising ability WeaponFlyingAbilityTag = Tag.RequestTag(TagsManager,"abilities.weapon.flying"); _weaponFlyingAbility = Abilities.GrantAbilityPermanently(FlyingTickAbility.GetAbilityData(), 1, LevelComparison.None, this); Events.Subscribe(WeaponStoppedFlyingEventTag, _ => { _weaponFlyingAbility.Cancel(); }); BodyEntered += OnThrownWeaponReachesGround; InHandState.StateExited += WeaponLeft; InHandState.StateEntered += WeaponBack; #region EventRaising _handToFlying.Taken += () => { Events.Raise(new EventData { EventTags = WeaponHandToFlyingEventTag.GetSingleTagContainer()!, Source = this }); Events.Raise(new EventData { EventTags = WeaponStartedFlyingEventTag.GetSingleTagContainer()!, Source = this }); }; _flyingToHand.Taken += () => { Events.Raise(new EventData { EventTags = WeaponFlyingToHandEventTag.GetSingleTagContainer()!, Source = this }); Events.Raise(new EventData { EventTags = WeaponStoppedFlyingEventTag.GetSingleTagContainer()!, Source = this }); }; _plantedToHand.Taken += () => { Events.Raise(new EventData { EventTags = WeaponPlantedToHandEventTag.GetSingleTagContainer()!, Source = this }); }; _plantedToFlying.Taken += () => { Events.Raise(new EventData { EventTags = WeaponPlantedToFlyingEventTag.GetSingleTagContainer()!, Source = this }); Events.Raise(new EventData { EventTags = WeaponStartedFlyingEventTag.GetSingleTagContainer()!, Source = this }); }; _toPlanted.Taken += () => { Events.Raise(new EventData { EventTags = WeaponPlantedEventTag.GetSingleTagContainer()!, Source = this, Target = _plantedEntity }); Events.Raise(new EventData { EventTags = WeaponStoppedFlyingEventTag.GetSingleTagContainer()!, Source = this }); }; #endregion } public void GrantNewAbilityForEvent(WeaponEvent forEvent, ForgeAbilityBehavior abilityBehavior) { var relevantMap = GetGrantedAbilities(forEvent); if (relevantMap.ContainsKey(abilityBehavior)) return; var eventTagsMap = new Dictionary { { WeaponEvent.StartedFlying, WeaponStartedFlyingEventTag }, { WeaponEvent.StoppedFlying, WeaponStoppedFlyingEventTag }, { WeaponEvent.FlyingTick, WeaponFlyingTickEventTag }, }; var ability = new AbilityData( "Ability", behaviorFactory: abilityBehavior.GetBehavior, abilityTriggerData: AbilityTriggerData.ForEvent(eventTagsMap[forEvent])); var leftGrantAbilityConfig = new GrantAbilityConfig( ability, ScalableLevel: new ScalableInt(1), RemovalPolicy: AbilityDeactivationPolicy.CancelImmediately, InhibitionPolicy: AbilityDeactivationPolicy.CancelImmediately, TryActivateOnGrant: false, TryActivateOnEnable: false, LevelOverridePolicy: LevelComparison.Higher); var leftGrantComponent = new GrantAbilityEffectComponent([leftGrantAbilityConfig]); var leftGrantEffect = new EffectData( "Grant Weapon Ability", new DurationData(DurationType.Infinite), effectComponents: [leftGrantComponent]); var effectHandle = EffectsManager.ApplyEffect(new Effect(leftGrantEffect, new EffectOwnership(this, this))); if (effectHandle == null) return; relevantMap[abilityBehavior] = effectHandle; } public void RemoveAbilityForEvent(WeaponEvent forEvent, ForgeAbilityBehavior abilityBehavior) { var relevantMap = GetGrantedAbilities(forEvent); if (!relevantMap.TryGetValue(abilityBehavior, out var effectHandle)) return; EffectsManager.RemoveEffect(effectHandle); relevantMap.Remove(abilityBehavior); } public void RemoveAllEventAbilities() { foreach (var weaponEvent in Enum.GetValues()) { var abilities = GetGrantedAbilities(weaponEvent); foreach (var ability in abilities.Values) { EffectsManager.RemoveEffect(ability); } abilities.Clear(); } } public Dictionary GetGrantedAbilities(WeaponEvent forEvent) { var abilitiesMap = new Dictionary> { { WeaponEvent.StartedFlying, _grantedWeaponStartedFlyingAbilities }, { WeaponEvent.StoppedFlying, _grantedWeaponStoppedFlyingAbilities }, { WeaponEvent.FlyingTick, _grantedWeaponFlyingTickAbilities }, }; return abilitiesMap[forEvent]; } 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) { Abilities.UpdateAbilities(delta); EffectsManager.UpdateEffects(delta); if (!FlyingState.Active) return; WeaponMesh.Rotation = new Vector3(WeaponMesh.Rotation.X, WeaponMesh.Rotation.Y + (float) delta * 100, WeaponMesh.Rotation.Z); //GD.Print(Attributes["WeaponAttributeSet.Level"].CurrentValue); //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; } }