using Godot; using System; using System.Diagnostics; using System.Linq; using Cirno.Scripts; using Cirno.Scripts.Components; using Cirno.Scripts.Components.Actors; using Cirno.Scripts.Resources; using Godot.Collections; using System.Threading.Tasks; using Cirno.Scripts.Controllers; public partial class PlayerMovement : CharacterBody2D, IDestructible { [Export] public int Speed { get; set; } = 400; [Export] public int StrafeSpeed { get; set; } = 200; public int MovementSpeed => _isStrafing ? StrafeSpeed : Speed; [Export] public float CrosshairDistance { get; set; } = 10f; [Export] public PackedScene SelectorScene { get; set; } [Export] public string GameOverScene { get; set; } [Export] public Texture2D WingsSprite { get; set; } private Selector _selector; //private Interactable _lastInteractable; [Export] public Marker2D Muzzle { get; set; } private AnimatedSprite2D _animatedSprite; private Vector2 _movementDirection { get; set; } private Vector2 _facingDirection { get; set; } private Vector2 _rightStickInput { get; set; } private Sprite2D _crosshair; [Export] public float MaxHealth = 32f; [Export] public float MaxShield = 32f; [Export] public Shader BlinkShader { get; set; } [Export] public Sprite2D HitboxSprite { get; set; } [Export] public BulletOwner BulletGroup { get; set; } = BulletOwner.Player; [ExportGroup("Action Names")] [Export] private string _shootActionName = "shoot"; [Export] private string _useActionName = "Use"; [Export] private string _strafeActionName = "strafe"; [Export] private string _nextWeaponActionName = "next_weapon"; [Export] private string _previousWeaponActionName = "previous_weapon"; [ExportCategory("Particles")] [Export] private PackedScene _deathParticles; [Export] private GpuParticles2D _shieldParticles; private PlayerState _state; private bool _isDestroyed = false; private GameManager _gameManager; private InventoryManager _inventoryManager; private ActorResourceProvider _healthProvider; private ActorResourceProvider _shieldProvider; private bool _isStrafing { get; set; } private bool _canMove = true; public Weapon EquippedWeapon => _weaponProvider.EquippedWeapon; [Signal] public delegate void HealthChangedEventHandler(float newHealth, float maxHealth); [Signal] public delegate void ShieldChangedEventHandler(float newShield, float maxShield); [Signal] public delegate void InteractableAreaEnteredEventHandler(Interactable interactable); [Signal] public delegate void InteractableAreaExitedEventHandler(Interactable interactable); [Signal] public delegate void DeathEventHandler(); public float CurrentHealth { get => _healthProvider.CurrentResource; set => _healthProvider.CurrentResource = value; } public float CurrentShield { get => _shieldProvider.CurrentResource; set => _shieldProvider.CurrentResource = value; } private Vector2 _lastCheckPointPosition; public Vector2 LastCheckPointPosition { get => _lastCheckPointPosition; set => _lastCheckPointPosition = value; } private PlayerWeaponProvider _weaponProvider; public override void _Ready() { // CurrentHealth = MaxHealth; // CurrentShield = MaxShield; _state = PlayerState.Active; _weaponProvider = GetNode("WeaponProvider"); _weaponProvider.Init(this); _animatedSprite = GetNode("./Smoothing2D/AnimatedSprite2D"); _crosshair = GetNode("./Smoothing2D/Crosshair"); _healthProvider = GetNode("HealthProvider"); _shieldProvider = GetNode("ShieldProvider"); _healthProvider.MaxResource = MaxHealth; _shieldProvider.MaxResource = MaxShield; _healthProvider.ResourceChanged += (value, maxValue) => { EmitSignal(nameof(HealthChanged), value, maxValue); }; _shieldProvider.ResourceChanged += (value, maxValue) => { EmitSignal(nameof(ShieldChanged), value, maxValue); }; _healthProvider.FillResource(); _shieldProvider.FillResource(); _movementDirection = Vector2.Zero; _facingDirection = Vector2.Zero; _rightStickInput = Vector2.Zero; _isStrafing = false; _gameManager = GameManager.Instance; //_gameManager = this.GetGameManager(); _inventoryManager = this.GetInventoryManager(); _gameManager.GameStateChange += GameManagerOnGameStateChange; if (SelectorScene != null) { _selector = this.CreateSibling(SelectorScene, this.GlobalPosition); _selector.Visible = false; } _inventoryManager.ItemUsed += this.UseItem; _lastCheckPointPosition = GlobalPosition; _ = UnTeleport(); } private void GameManagerOnGameStateChange(GameState state) { switch (state) { case GameState.Menu: case GameState.Paused: _canMove = false; break; case GameState.Playing: _canMove = true; _state = PlayerState.Active; break; case GameState.Dialogue: _canMove = false; break; case GameState.Controlling: _canMove = false; _state = PlayerState.Controlling; break; } } /// /// Requests disable movement /// /// true disables false enables public void RequestMovementDisable(bool disable) { if (disable) { _canMove = false; } else { _canMove = true; } } public void AddWeapon(Weapon weapon) { _weaponProvider.Equip(weapon, false); } public void EquipWeapon(string itemKey) { _weaponProvider.Equip(itemKey, true); } public void UseItem(LootItem item, int currentAmount) { GD.Print($"Used item on player {item.ItemKey}"); switch (item.Item) { case ItemTypes.FrogBomb: _inventoryManager.RemoveItem(item.ItemKey, 1); // emit projectile var bulletData = item.WeaponData.MakeBullet(this.GlobalPosition); var bullet = PoolingManager.Instance.SpawnBullet(bulletData.OriginalBulletResource); bullet.GlobalPosition = this.GlobalPosition; //var bullet = this.CreateChildOf(_gameManager.BulletsContainer, item.WeaponData.BulletData.BulletScene, this.GlobalPosition); bullet.Initialize(bulletData); //bullet.SetDirection(ShootDirection); bullet.SetDirection(_facingDirection); bullet.Speed = item.WeaponData.BulletData.BulletSpeed; RequestMovementDisable(true); // set camera _gameManager.CameraTargetObject(bullet); // set event destroy bullet.OnDestroy += () => { _gameManager.CameraTargetPlayer(); RequestMovementDisable(false); }; // go back break; } //var item = _inventoryManager.getitem } // Triggered by event in inventorymanager public void EquipWeapon(Weapon weapon) { _weaponProvider.Equip(weapon, true); } private void FindInteractable() { var selected = _selector.SelectedInteractable; if (!Input.IsActionJustPressed(_useActionName) || selected == null) return; if (!selected.CanActivate()) return; bool success = selected.Activate(); if (success) { // Deselect and scan for next _selector.RemoveInteractable(selected); //_selector.SelectedInteractable = null; //_selector.SelectNext(); } //var spaceState = GetWorld2D().DirectSpaceState; //var query = PhysicsRayQueryParameters2D.Create(Vector2.Zero, ) } private void HandleShoot() { if (!Input.IsActionPressed(_shootActionName)) return; _weaponProvider.Shoot(this._facingDirection); } private void SetAnimation() { if (Velocity.X == 0 && Velocity.Y == 0) { _animatedSprite.SpeedScale = 0; } else { _animatedSprite.SpeedScale = 1; } if (Velocity.X > 0) { _animatedSprite.Play("walk_right"); } else if (Velocity.X < 0) { _animatedSprite.Play("walk_left"); } else if (Velocity.Y > 0) { _animatedSprite.Play("walk_down"); } else if (Velocity.Y < 0) { _animatedSprite.Play("walk_up"); } } public Vector2 GetInput() { return Input.GetVector("left", "right", "up", "down"); } private Vector2 GetRightStickInput() { return new Vector2( Input.GetAxis("aim_left", "aim_right"), Input.GetAxis("aim_up", "aim_down") ); } private Vector2 CalculateCrosshairPosition() { return _facingDirection * CrosshairDistance;// + this.Position; //var angle = Mathf.Atan2(this.Position.X, this.Position.Y); //var cPos = new Vector2(this.Position.X + CrosshairDistance * Godot.Mathf.Cos(angle), this.Position.Y + CrosshairDistance * Godot.Mathf.Sin(angle)); } public override void _Process(double delta) { if (_isDestroyed) { if (Input.IsActionJustPressed(_shootActionName)) Respawn(); return; } ; SetAnimation(); if (!_canMove) return; _movementDirection = GetInput(); _isStrafing = Input.IsActionPressed(_strafeActionName); // Toggle visibility of the hitbox sprite based on strafing if (HitboxSprite != null) { HitboxSprite.Visible = _isStrafing; } _rightStickInput = GetRightStickInput(); // Update Facing Direction if (!_isStrafing) { if (_rightStickInput.Length() > 0.1f) // If the right stick is moved { _facingDirection = _rightStickInput.Normalized(); } else if (_movementDirection != Vector2.Zero) // Fall back to movement direction { _facingDirection = _movementDirection; } } HandleShoot(); FindInteractable(); if (Input.IsActionJustPressed(_nextWeaponActionName)) { _weaponProvider.NextWeapon(); } if (Input.IsActionJustPressed(_previousWeaponActionName)) { _weaponProvider.PreviousWeapon(); } _crosshair.Position = CalculateCrosshairPosition(); } public void Respawn() { if (!_isDestroyed) return; _isDestroyed = false; this.GlobalPosition = LastCheckPointPosition; _healthProvider.FillResource(); this.Visible = true; } public override void _PhysicsProcess(double delta) { if (_isDestroyed) return; if (!_canMove) return; Velocity = _movementDirection * MovementSpeed; MoveAndSlide(); } private void _on_interaction_controller_area_entered(Area2D area) { if (!_canMove) return; // Replace with function body. if (area.IsInGroup("Interactable") && area is Interactable interactable && interactable.CanActivate()) { Debug.WriteLine($"Interactable {area.Name} Entered"); EmitSignal(nameof(InteractableAreaEntered), interactable); if (_selector == null) return; _selector.AddInteractable(interactable); //_selector.SelectedInteractable = interactable; } } private void _on_interaction_controller_area_exited(Area2D area) { if (!_canMove) return; if (area.IsInGroup("Interactable") && area is Interactable interactable) { Debug.WriteLine($"Interactable {area.Name} Exited"); EmitSignal(nameof(InteractableAreaExited), interactable); if (_selector == null) return; _selector.RemoveInteractable(interactable); } } private void Explode() { Debug.WriteLine("Ded"); //CreateParticles(); //CreateDebris(); // if (GameOverScene != null) // { // GetTree().ChangeSceneToFile(GameOverScene); // } // else // { // QueueFree(); // } var particles = this.CreateSibling(_deathParticles, this.GlobalPosition); //particles.Init(); this.Visible = false; EmitSignal(SignalName.Death); } public void Hit(float damage, DamageType type = DamageType.Neutral) { if (!_canMove) return; GD.Print($"Player damaged for {damage}"); if (_isDestroyed) return; if (CurrentShield > 0 && type is not DamageType.Explosive or DamageType.Acid) { // Reduce shield PlayShieldAnimation(); CurrentShield -= damage; if (CurrentShield < 0) { CurrentHealth -= Math.Abs(CurrentShield); CurrentShield = 0; } } else { if (type is DamageType.Fire) { CurrentHealth -= damage * 2; } else { CurrentHealth -= damage; } Blink(); } if (!(CurrentHealth <= 0)) return; _isDestroyed = true; Explode(); } private void PlayShieldAnimation() { if (_shieldParticles is null) return; _shieldParticles.Emitting = true; } public void Blink() { if (BlinkShader != null) { _ = BlinkAsync(); } } private async Task BlinkAsync() { ((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader; Tween tween = GetTree().CreateTween(); tween.TweenMethod(Callable.From((float value) => SetShaderBlinkIntensity(value)), 1f, 0, 0.5); await ToSignal(tween, "finished"); } public async Task Teleport() { ((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader; Tween tween = GetTree().CreateTween(); tween.TweenMethod(Callable.From((float value) => SetShaderScanlineDensity(value)), 0f, 50f, 0.5); tween.Parallel().TweenMethod(Callable.From((float value) => SetShaderTeleportProgress(value)), 0f, 1f, 0.5); await ToSignal(tween, "finished"); } public async Task UnTeleport() { ((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader; Tween tween = GetTree().CreateTween(); tween.TweenMethod(Callable.From((float value) => SetShaderTeleportProgress(value)), 1f, 0f, 0.5); tween.Parallel().TweenMethod(Callable.From((float value) => SetShaderScanlineDensity(value)), 50f, 0f, 0.5); await ToSignal(tween, "finished"); } private void SetShaderTeleportProgress(float value) { ((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("teleport_progress", value); } private void SetShaderScanlineDensity(float value) { ((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("scanline_density", value); } private void SetShaderBlinkIntensity(float newValue) { ((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("blink_intensity", newValue); } public bool IsDestroyed() { return _isDestroyed; } private void _on_damage_hit_box_area_entered(Area2D area) { if (!_canMove) return; if (area is Bullet bullet && bullet.BulletOwner != BulletGroup) { if (!bullet.Enabled) return; this.Hit(bullet.Damage, bullet.DamageType); bullet.RequestCollisionDestruction(); } } } public enum PlayerState { Init, Active, Cutscene, Teleporting, UnTeleporting, Controlling, Dead, Drowning, }