Controllable enemies

This commit is contained in:
Marco 2025-03-21 17:52:01 +01:00
commit 0aad79a0f8
18 changed files with 893 additions and 23 deletions

View file

@ -0,0 +1,140 @@
using Cirno.Scripts.Components.Actors;
using Cirno.Scripts.Enums;
using Godot;
namespace Cirno.Scripts.Components.FSM.Enemy;
public partial class Controlled : EnemyStateBase
{
public override EnemyState StateId => EnemyState.Controlled;
[Export]
public EnemyStorageModule StorageModule { get; private set; }
[Export]
public GenericDamageReceiver DamageReceiver { get; private set; }
[Export]
private InputProvider _inputProvider;
[Export]
public PlayerCrosshairProvider CrosshairProvider { get; private set; }
[Export] public Weapon EquippedWeapon { get; private set; }
[Export] public StringName ControlEndAction { get; private set; } = "pause";
[Export] public StringName ShootAction { get; private set; } = "shoot";
private bool _isStrafing = false;
public override void EnterState()
{
base.EnterState();
GD.Print($"{StorageModule.Root.Name} Controlled");
DamageReceiver.ChangeState(true);
DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted;
GameManager.Instance.CameraTargetObject(MainObject);
GameManager.Instance.Player.SetState(PlayerState.Controlling);
DamageReceiver.BulletGroup = BulletOwner.Player;
_isStrafing = false;
CrosshairProvider.Visible = true;
// Show possession sprite
}
private void HealthProviderOnResourceDepleted()
{
ChangeState(EnemyState.Dead);
}
public override void ExitState()
{
base.ExitState();
DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
DamageReceiver.ChangeState(false);
GameManager.Instance.CameraTargetPlayer();
GameManager.Instance.Player.SetState(PlayerState.Active);
DamageReceiver.BulletGroup = BulletOwner.Enemy;
CrosshairProvider.Visible = false;
// Hide possession sprite
}
public override void PhysicsProcessState(double delta)
{
base.PhysicsProcessState(delta);
if (_inputProvider.GetShootPressed())
{
Shoot();
}
if (Input.IsActionJustPressed(ControlEndAction))
{
// if (GameManager.Instance.ToggleControlMode() is GameState.Playing)
// {
// ResumeControl();
// }
StateMachine.SetState(EnemyState.Idle);
}
HandlePhysics(delta);
StorageModule.FacingDirection = MainObject.Velocity.SnapToCardinal().Normalized();
}
private void HandlePhysics(double delta)
{
StorageModule.MovementDirection = _inputProvider.GetMovementInput().Normalized();
_isStrafing = _inputProvider.GetStrafePressed();
var rightStickInput = _inputProvider.GetAimInput().Normalized();
if (rightStickInput.Length() > 0.1f) // If the right stick is moved
{
StorageModule.AimingDirection = rightStickInput;
}
else if (StorageModule.MovementDirection != Vector2.Zero) // Fall back to movement direction
{
StorageModule.AimingDirection = StorageModule.MovementDirection;
}
// Update crosshair
CrosshairProvider.UpdatePosition(StorageModule.AimingDirection);
MainObject.Velocity = StorageModule.MovementDirection * StorageModule.MovementSpeed;
MainObject.MoveAndSlide();
}
private void Shoot()
{
if (EquippedWeapon == null) return;
var direction = StorageModule.AimingDirection;
// Shoot at the player's last known position
EquippedWeapon.ShootDirection = direction;
//StorageModule.FacingDirection = direction;
//StorageModule.FacingDirection = direction.SnapToCardinal().Normalized();
EquippedWeapon.Shoot(BulletOwner.Player);
}
public override void ProcessState(double delta)
{
base.ProcessState(delta);
}
}

View file

@ -0,0 +1 @@
uid://dj37rup1ibnn6

View file

@ -1,4 +1,5 @@
using Cirno.Scripts.Enums;
using System;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Resources.Loot;
using Godot;
@ -6,7 +7,7 @@ using Godot.Collections;
namespace Cirno.Scripts.Components.FSM.Enemy;
public partial class EnemyFSMProxy : CharacterBody2D
public partial class EnemyFSMProxy : CharacterBody2D, IActivable
{
[Export] public EnemyStateMachine EnemyFSM { get; private set; }
@ -21,4 +22,31 @@ public partial class EnemyFSMProxy : CharacterBody2D
[Export] public Node2D DefeatScript { get; set; }
[Export] public ActivationType ActivationType { get; private set; } = ActivationType.Toggle;
public bool Activate(ActivationType activationType = ActivationType.Toggle)
{
switch (activationType)
{
case ActivationType.Toggle:
EnemyFSM.SetState(EnemyState.Controlled);
break;
case ActivationType.Enable:
// Enable or disable AI
break;
case ActivationType.Disable:
// Enable or disable AI
break;
case ActivationType.Use:
break;
case ActivationType.Destroy:
EnemyFSM.SetState(EnemyState.Dead);
break;
case ActivationType.Open:
break;
case ActivationType.Close:
break;
}
return true;
}
}

View file

@ -13,11 +13,13 @@ public partial class EnemyStorageModule : Node2D
public Vector2 MovementDirection { get; set; }
public Vector2 FacingDirection { get; set; }
public Vector2 AimingDirection { get; set; }
public float MovementSpeed => Root.EnemyResource.MovementSpeed;
public IEnumerable<LootDrop> LootDrops => Root.EnemyResource.LootDrops.Concat(Root.ExtraLoot);
public AiState AiState { get; set; }
}

View file

@ -47,6 +47,7 @@ public partial class Idle : EnemyStateBase
private void HealthProviderOnResourceDecreased(float oldvalue, float newvalue, float maxvalue)
{
StorageModule.AiState = AiState.Enabled;
ChangeState(EnemyState.Alert);
}
@ -85,7 +86,7 @@ public partial class Idle : EnemyStateBase
public override void PhysicsProcessState(double delta)
{
base.PhysicsProcessState(delta);
if (_isPlayerInRange)
if (StorageModule.AiState is AiState.Enabled && _isPlayerInRange)
{
if (PlayerDetection.IsPlayerInSight())
{

View file

@ -18,6 +18,11 @@ public partial class Init : EnemyStateBase
{
GD.Print("Enemy init");
DamageReceiver.HealthProvider.MaxResource = StorageModule.Root.EnemyResource.MaxHealth;
StorageModule.AiState = StorageModule.Root.StartingAiState;
// TODO: Hide wings
// TODO: Hide aiming reticule
StateMachine.SetState(EnemyState.Idle);
}