cirnogodot/Scripts/PlayerMovement.cs
2025-03-11 17:58:46 +01:00

592 lines
No EOL
14 KiB
C#

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;
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<PlayerWeaponProvider>("WeaponProvider");
_weaponProvider.Init(this);
_animatedSprite = GetNode<AnimatedSprite2D>("./Smoothing2D/AnimatedSprite2D");
_crosshair = GetNode<Sprite2D>("./Smoothing2D/Crosshair");
_healthProvider = GetNode<ActorResourceProvider>("HealthProvider");
_shieldProvider = GetNode<ActorResourceProvider>("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<Selector>(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;
}
}
/// <summary>
/// Requests disable movement
/// </summary>
/// <param name="disable">true disables false enables</param>
public void RequestMovementDisable(bool disable)
{
if (disable)
{
_canMove = false;
}
else
{
_canMove = true;
}
}
public void AddWeapon(Weapon weapon)
{
_weaponProvider.AddWeapon(weapon);
}
public void EquipWeapon(string itemKey)
{
_weaponProvider.EquipWeapon(itemKey);
}
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 bullet = this.CreateChildOf<Bullet>(_gameManager.BulletsContainer, item.WeaponData.BulletData.BulletScene, this.GlobalPosition);
var bulletData = item.WeaponData.MakeBullet(this.GlobalPosition);
bullet.Initialize(bulletData, _gameManager);
//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.EquipWeapon(weapon);
}
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<AutodeleteParticle>(_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)
{
this.Hit(bullet.Damage, bullet.DamageType);
bullet.RequestCollisionDestruction();
}
}
}
public enum PlayerState
{
Init,
Active,
Cutscene,
Teleporting,
UnTeleporting,
Controlling,
Dead,
Drowning,
}