|
|
|
|
@@ -54,186 +54,177 @@ public partial class BossTypeA : CharacterBody3D, IEnemy, IHasPrimaryAttack, IHa
|
|
|
|
|
|
|
|
|
|
public void Setup()
|
|
|
|
|
{
|
|
|
|
|
_enemyLogic = new EnemyLogic();
|
|
|
|
|
_enemyLogic.Set(_enemyStatResource);
|
|
|
|
|
_enemyLogic.Set(this as IEnemy);
|
|
|
|
|
_enemyLogic.Set(_player);
|
|
|
|
|
_damageCalculator = new DamageCalculator();
|
|
|
|
|
SetPhysicsProcess(true);
|
|
|
|
|
_enemyLogic = new EnemyLogic();
|
|
|
|
|
_enemyLogic.Set(_enemyStatResource);
|
|
|
|
|
_enemyLogic.Set(this as IEnemy);
|
|
|
|
|
_enemyLogic.Set(_player);
|
|
|
|
|
_damageCalculator = new DamageCalculator();
|
|
|
|
|
SetPhysicsProcess(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnResolved()
|
|
|
|
|
{
|
|
|
|
|
EnemyBinding = _enemyLogic.Bind();
|
|
|
|
|
EnemyBinding = _enemyLogic.Bind();
|
|
|
|
|
|
|
|
|
|
EnemyBinding
|
|
|
|
|
.Handle((in EnemyLogic.Output.TakeAction _) =>
|
|
|
|
|
{
|
|
|
|
|
TakeAction();
|
|
|
|
|
})
|
|
|
|
|
.Handle((in EnemyLogic.Output.Defeated output) =>
|
|
|
|
|
{
|
|
|
|
|
});
|
|
|
|
|
EnemyBinding
|
|
|
|
|
.Handle((in EnemyLogic.Output.TakeAction _) =>
|
|
|
|
|
{
|
|
|
|
|
TakeAction();
|
|
|
|
|
})
|
|
|
|
|
.Handle((in EnemyLogic.Output.Defeated output) =>
|
|
|
|
|
{
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.Provide();
|
|
|
|
|
this.Provide();
|
|
|
|
|
|
|
|
|
|
_enemyLogic.Start();
|
|
|
|
|
_enemyLogic.Start();
|
|
|
|
|
|
|
|
|
|
CurrentHP = new AutoProp<double>(_enemyStatResource.MaximumHP);
|
|
|
|
|
CurrentHP.Sync += OnHPChanged;
|
|
|
|
|
CurrentHP = new AutoProp<double>(_enemyStatResource.MaximumHP);
|
|
|
|
|
CurrentHP.Sync += OnHPChanged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnHPChanged(double newHP)
|
|
|
|
|
{
|
|
|
|
|
if (newHP <= 0)
|
|
|
|
|
Die();
|
|
|
|
|
if (newHP <= 0)
|
|
|
|
|
Die();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual void Die()
|
|
|
|
|
{
|
|
|
|
|
SetProcess(false);
|
|
|
|
|
_movementSpeed = 0;
|
|
|
|
|
CurrentHP.OnNext(0);
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
|
|
|
|
|
_collisionShape.SetDeferred("disabled", true);
|
|
|
|
|
_enemyModelView.PlayDeathAnimation();
|
|
|
|
|
var tweener = CreateTween();
|
|
|
|
|
tweener.TweenInterval(1.0f);
|
|
|
|
|
tweener.TweenCallback(Callable.From(QueueFree));
|
|
|
|
|
SetProcess(false);
|
|
|
|
|
_movementSpeed = 0;
|
|
|
|
|
CurrentHP.OnNext(0);
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
|
|
|
|
|
_collisionShape.SetDeferred("disabled", true);
|
|
|
|
|
_enemyModelView.PlayDeathAnimation();
|
|
|
|
|
var tweener = CreateTween();
|
|
|
|
|
tweener.TweenInterval(1.0f);
|
|
|
|
|
tweener.TweenCallback(Callable.From(QueueFree));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnReady()
|
|
|
|
|
{
|
|
|
|
|
_target = GlobalPosition;
|
|
|
|
|
SetPhysicsProcess(true);
|
|
|
|
|
_enemyModelView.Hitbox.AreaEntered += Hitbox_AreaEntered;
|
|
|
|
|
_attackTimer.Start();
|
|
|
|
|
_target = GlobalPosition;
|
|
|
|
|
SetPhysicsProcess(true);
|
|
|
|
|
_enemyModelView.Hitbox.AreaEntered += Hitbox_AreaEntered;
|
|
|
|
|
_attackTimer.Start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnPhysicsProcess(double delta)
|
|
|
|
|
{
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
|
|
|
|
|
|
|
|
|
|
var direction = GlobalPosition.DirectionTo(_target).Normalized();
|
|
|
|
|
var direction = GlobalPosition.DirectionTo(_target).Normalized();
|
|
|
|
|
|
|
|
|
|
if (_enemyLogic.Value is not EnemyLogic.State.Activated)
|
|
|
|
|
return;
|
|
|
|
|
if (_enemyLogic.Value is not EnemyLogic.State.Activated)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var rotationAngle = GetRotationAngle();
|
|
|
|
|
if (GlobalBasis.Z.AngleTo(_player.CurrentBasis.Z) > Mathf.DegToRad(60))
|
|
|
|
|
{
|
|
|
|
|
RotateToPlayer(rotationAngle);
|
|
|
|
|
_enemyModelView.PlayIdleAnimation();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var rotationAngle = GetRotationAngle();
|
|
|
|
|
if (GlobalBasis.Z.AngleTo(_player.CurrentBasis.Z) > Mathf.DegToRad(60))
|
|
|
|
|
{
|
|
|
|
|
RotateToPlayer(rotationAngle);
|
|
|
|
|
_enemyModelView.PlayIdleAnimation();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 5f)
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
|
|
|
|
|
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 5f)
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
|
|
|
|
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 5f)
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
|
|
|
|
|
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 5f)
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
|
|
|
|
|
|
|
|
|
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer)
|
|
|
|
|
{
|
|
|
|
|
Velocity = direction * _movementSpeed;
|
|
|
|
|
MoveAndSlide();
|
|
|
|
|
_enemyModelView.PlayWalkAnimation();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_enemyModelView.PlayIdleAnimation();
|
|
|
|
|
}
|
|
|
|
|
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer)
|
|
|
|
|
{
|
|
|
|
|
Velocity = direction * _movementSpeed;
|
|
|
|
|
MoveAndSlide();
|
|
|
|
|
_enemyModelView.PlayWalkAnimation();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_enemyModelView.PlayIdleAnimation();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void TakeAction()
|
|
|
|
|
{
|
|
|
|
|
var rng = new RandomNumberGenerator();
|
|
|
|
|
var options = new List<Action>() { PrimaryAttack, SecondaryAttack };
|
|
|
|
|
var selection = rng.RandWeighted([0.875f, 0.125f]);
|
|
|
|
|
options[(int)selection].Invoke();
|
|
|
|
|
var rng = new RandomNumberGenerator();
|
|
|
|
|
var options = new List<Action>() { PrimaryAttack, SecondaryAttack };
|
|
|
|
|
var selection = rng.RandWeighted([0.875f, 0.125f]);
|
|
|
|
|
options[(int)selection].Invoke();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void PrimaryAttack()
|
|
|
|
|
{
|
|
|
|
|
_enemyModelView.PlayPrimaryAttackAnimation();
|
|
|
|
|
_enemyModelView.PlayPrimaryAttackAnimation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SecondaryAttack()
|
|
|
|
|
{
|
|
|
|
|
_enemyModelView.PlaySecondaryAttackAnimation();
|
|
|
|
|
_enemyModelView.PlaySecondaryAttackAnimation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StartAttackTimer()
|
|
|
|
|
{
|
|
|
|
|
_attackTimer.Timeout += OnAttackTimeout;
|
|
|
|
|
_attackTimer.Timeout += OnAttackTimeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StopAttackTimer()
|
|
|
|
|
{
|
|
|
|
|
if (_attackTimer.TimeLeft > 0)
|
|
|
|
|
_attackTimer.Timeout -= OnAttackTimeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void SetTarget(Vector3 target) => _target = target;
|
|
|
|
|
|
|
|
|
|
private void Hitbox_AreaEntered(Area3D area)
|
|
|
|
|
{
|
|
|
|
|
var target = area.GetOwner();
|
|
|
|
|
if (target is IPlayer player)
|
|
|
|
|
{
|
|
|
|
|
var damage = _enemyStatResource.CurrentAttack * PrimaryAttackElementalDamageBonus;
|
|
|
|
|
player.TakeDamage(damage, PrimaryAttackElementalType, BattleExtensions.IsCriticalHit(_enemyStatResource.Luck));
|
|
|
|
|
}
|
|
|
|
|
var target = area.GetOwner();
|
|
|
|
|
if (target is IPlayer player)
|
|
|
|
|
{
|
|
|
|
|
var damage = _enemyStatResource.CurrentAttack * PrimaryAttackElementalDamageBonus;
|
|
|
|
|
player.TakeDamage(damage, PrimaryAttackElementalType, BattleExtensions.IsCriticalHit(_enemyStatResource.Luck));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StartFight()
|
|
|
|
|
{
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Activate()
|
|
|
|
|
{
|
|
|
|
|
Show();
|
|
|
|
|
EnemyHitbox.SetDeferred(CollisionShape3D.PropertyName.Disabled, false);
|
|
|
|
|
Show();
|
|
|
|
|
EnemyHitbox.SetDeferred(CollisionShape3D.PropertyName.Disabled, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnAttackTimeout()
|
|
|
|
|
{
|
|
|
|
|
if (GlobalPosition.DistanceTo(_player.CurrentPosition) > 5f)
|
|
|
|
|
{
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (GlobalPosition.DistanceTo(_player.CurrentPosition) > 5f)
|
|
|
|
|
{
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var rng = new RandomNumberGenerator();
|
|
|
|
|
rng.Randomize();
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.AttackTimer());
|
|
|
|
|
_attackTimer.Stop();
|
|
|
|
|
_attackTimer.WaitTime = rng.RandfRange(2f, 5.0f);
|
|
|
|
|
_attackTimer.Start();
|
|
|
|
|
var rng = new RandomNumberGenerator();
|
|
|
|
|
rng.Randomize();
|
|
|
|
|
_enemyLogic.Input(new EnemyLogic.Input.AttackTimer());
|
|
|
|
|
_attackTimer.Stop();
|
|
|
|
|
_attackTimer.WaitTime = rng.RandfRange(2f, 5.0f);
|
|
|
|
|
_attackTimer.Start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RotateToPlayer(float rotationAngle)
|
|
|
|
|
{
|
|
|
|
|
var tweener = GetTree().CreateTween();
|
|
|
|
|
tweener.TweenMethod(Callable.From((float x) => RotateTowardsPlayer(x)), Rotation.Y, rotationAngle, 0.5f);
|
|
|
|
|
var tweener = GetTree().CreateTween();
|
|
|
|
|
tweener.TweenMethod(Callable.From((float x) => RotateTowardsPlayer(x)), Rotation.Y, rotationAngle, 0.5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private float GetRotationAngle()
|
|
|
|
|
{
|
|
|
|
|
var target = new Vector3(_player.CurrentPosition.X, Position.Y, _player.CurrentPosition.Z);
|
|
|
|
|
_rotation.LookAt(target, Vector3.Up, true);
|
|
|
|
|
_rotation.RotateY(Rotation.Y);
|
|
|
|
|
return _rotation.Rotation.Y;
|
|
|
|
|
var target = new Vector3(_player.CurrentPosition.X, Position.Y, _player.CurrentPosition.Z);
|
|
|
|
|
_rotation.LookAt(target, Vector3.Up, true);
|
|
|
|
|
_rotation.RotateY(Rotation.Y);
|
|
|
|
|
return _rotation.Rotation.Y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RotateTowardsPlayer(float angle) => Rotation = new Vector3(Rotation.X, angle, Rotation.Z);
|
|
|
|
|
|
|
|
|
|
public override void _ExitTree()
|
|
|
|
|
{
|
|
|
|
|
CurrentHP.OnCompleted();
|
|
|
|
|
StopAttackTimer();
|
|
|
|
|
CurrentHP.OnCompleted();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Move(Vector3 velocity) => throw new NotImplementedException();
|
|
|
|
|
|