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, 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 ShapeCast3D _dashCast3D = 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 WeaponLandTag; 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")); WeaponLocationIndicator = GetNode("WeaponLocationIndicator"); WeaponLocationIndicator.Visible = false; WeaponLocationIndicatorMaterial = (WeaponLocationIndicator.GetActiveMaterial(0) as StandardMaterial3D)!; WeaponMesh = GetNode("Weapon"); _startMeshRotation = WeaponMesh.Rotation; _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; InHandState.StateExited += WeaponLeft; InHandState.StateEntered += WeaponBack; } 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"); // WeaponLocationIndicatorMaterial.StencilColor = new Color(1f, 1f, 1f); _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; } public void RaiseWeaponLandEvent(IForgeEntity? victim = null) { Events.Raise(new EventData { 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)); } } public void RethrowWeapon() { _weaponState.SendEvent("throw"); _throwDirection = Vector3.Up; ThrowWeaponOnCurve(); } public void ThrowWeaponOnCurve() { Freeze = false; ApplyImpulse(_throwDirection * ThrowForce); } public void PlantWeaponInWall() { _weaponState.SendEvent("plant"); Freeze = true; WeaponMesh.Rotation = _startMeshRotation; // WeaponLocationIndicatorMaterial.StencilColor = new Color(1f, 0.2f, 0.2f); if (PlantObject is Node3D node) { PlantInEnemy(node); } else RaiseWeaponLandEvent(); 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; } }