You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
9.0 KiB
320 lines
9.0 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading.Tasks;
|
|
using Babushka.scripts.CSharp.Common.Util;
|
|
using Godot;
|
|
|
|
namespace Babushka.scripts.CSharp.Common.Fight;
|
|
|
|
public class FightHappening
|
|
{
|
|
/*
|
|
To get a visual overview of the FightHappening state machine, refer to the graph on miro:
|
|
https://miro.com/app/board/uXjVK8YEprM=/?moveToWidget=3458764640805655262&cot=14
|
|
*/
|
|
|
|
#region Internal Types
|
|
|
|
public enum FightState
|
|
{
|
|
None,
|
|
FightStartAnim,
|
|
FightersEnter,
|
|
FightersEnterAnim,
|
|
NextFighter,
|
|
StateCheck,
|
|
InputActionSelect,
|
|
ActionCheckDetails,
|
|
InputActionDetail,
|
|
ActionExecute,
|
|
ActionAnim,
|
|
EnemyActionSelect,
|
|
PlayerWin,
|
|
EnemyWin,
|
|
}
|
|
|
|
private class FightersEnterStaging
|
|
{
|
|
public required List<FightWorld.Fighter> enteringAllyFighters;
|
|
public required List<FightWorld.Fighter> enteringEnemyFighters;
|
|
|
|
public bool HasAnyToExecute()
|
|
{
|
|
return enteringAllyFighters.Count != 0 || enteringEnemyFighters.Count != 0;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Settings
|
|
|
|
private const float StartAnimationTime = 1;
|
|
private const float FightersEnterAnimationTime = 1;
|
|
|
|
#endregion
|
|
|
|
#region ShortCuts
|
|
|
|
private static FightWorld.FightHappeningData HappeningData =>
|
|
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
|
|
|
|
private static FightWorld.Fighter CurrentFighter => HappeningData.fighterStack.Current;
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event Action<FightState>? transitionFromState;
|
|
public event Action<FightState, FightState>? transitionState;
|
|
public event Action<FightState>? transitionToState;
|
|
|
|
#endregion
|
|
|
|
#region Staging
|
|
|
|
private FightersEnterStaging? _fightersEnterStaging;
|
|
private FighterAction? _actionStaging;
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
public void StartFight()
|
|
{
|
|
RequireState(FightState.None);
|
|
ChangeState(FightState.FightStartAnim);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region State Machine
|
|
|
|
private void ChangeState(FightState nextState)
|
|
{
|
|
TransitionFromState();
|
|
var lastState = HappeningData.fightState;
|
|
HappeningData.fightState = nextState;
|
|
TransitionFromToState(nextState, lastState);
|
|
TransitionToState(nextState);
|
|
}
|
|
|
|
private void TransitionFromState()
|
|
{
|
|
// fixed behaviour
|
|
switch (HappeningData.fightState)
|
|
{
|
|
default: break;
|
|
}
|
|
|
|
// notify everyone else
|
|
transitionFromState?.Invoke(HappeningData.fightState);
|
|
}
|
|
|
|
private void TransitionFromToState(FightState nextState, FightState lastState)
|
|
{
|
|
transitionState?.Invoke(lastState, nextState);
|
|
}
|
|
|
|
private void TransitionToState(FightState nextState)
|
|
{
|
|
// notify everyone else
|
|
transitionToState?.Invoke(nextState);
|
|
|
|
// fixed behaviour
|
|
switch (HappeningData.fightState)
|
|
{
|
|
case FightState.FightStartAnim:
|
|
AdvanceToStateInSeconds(FightState.FightersEnter, StartAnimationTime);
|
|
break;
|
|
case FightState.FightersEnter:
|
|
_fightersEnterStaging = StageFightersEnter();
|
|
if (_fightersEnterStaging.HasAnyToExecute())
|
|
{
|
|
ExecuteFightersEnter();
|
|
ChangeState(FightState.FightersEnterAnim);
|
|
}
|
|
else
|
|
{
|
|
ChangeState(FightState.NextFighter);
|
|
}
|
|
|
|
break;
|
|
case FightState.FightersEnterAnim:
|
|
AdvanceToStateInSeconds(FightState.NextFighter, FightersEnterAnimationTime);
|
|
break;
|
|
case FightState.NextFighter:
|
|
ExecuteNextFighter();
|
|
ChangeState(FightState.StateCheck);
|
|
break;
|
|
case FightState.StateCheck:
|
|
// restest action staging
|
|
_actionStaging = null;
|
|
|
|
if ( /*TODO: are all allys dead*/ false)
|
|
{
|
|
ChangeState(FightState.EnemyWin);
|
|
}
|
|
else if (HappeningData.enemyGroup.AreAllDead())
|
|
{
|
|
ChangeState(FightState.PlayerWin);
|
|
}
|
|
else if (CurrentFighter.actionsLeft <= 0)
|
|
{
|
|
ChangeState(FightState.FightersEnter);
|
|
}
|
|
else if (CurrentFighter.isEnemy)
|
|
{
|
|
ChangeState(FightState.EnemyActionSelect);
|
|
}
|
|
else
|
|
{
|
|
ChangeState(FightState.InputActionSelect);
|
|
}
|
|
|
|
break;
|
|
case FightState.InputActionSelect:
|
|
// wait for player input
|
|
break;
|
|
case FightState.ActionCheckDetails:
|
|
if (ActionAbort())
|
|
ChangeState(FightState.InputActionSelect);
|
|
else if (ActionNeededDetail() != null)
|
|
ChangeState(FightState.InputActionDetail);
|
|
else
|
|
ChangeState(FightState.ActionExecute);
|
|
break;
|
|
case FightState.InputActionDetail:
|
|
// wait for player input
|
|
break;
|
|
case FightState.EnemyActionSelect:
|
|
_actionStaging = CurrentFighter.AutoSelectAction();
|
|
ChangeState(FightState.ActionExecute);
|
|
break;
|
|
case FightState.ActionExecute:
|
|
ExecuteAction();
|
|
ChangeState(FightState.ActionAnim);
|
|
break;
|
|
case FightState.ActionAnim:
|
|
var actionTime = GetActionAnimationEnd();
|
|
if (actionTime.IsType<float>())
|
|
{
|
|
AdvanceToStateInSeconds(FightState.StateCheck, actionTime);
|
|
}
|
|
else
|
|
{
|
|
_ = AdvanceToStateWhenDone(FightState.StateCheck, actionTime);
|
|
}
|
|
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Game Logic
|
|
|
|
private FightersEnterStaging StageFightersEnter()
|
|
{
|
|
// ally
|
|
var enteringAllyFighters = new List<FightWorld.Fighter>();
|
|
//TODO
|
|
|
|
// enemy
|
|
const int totalEnemySpace = 3;
|
|
var enemySpaceLeft = totalEnemySpace - HappeningData.enemyGroup.GetEnteredAmount();
|
|
var enterEnemyFighters = new List<FightWorld.Fighter>();
|
|
|
|
for (var i = 0; i < enemySpaceLeft; i++)
|
|
{
|
|
if (HappeningData.enemyGroup.TryGetFirstUnenteredFighter(out var fighter))
|
|
{
|
|
enterEnemyFighters.Add(fighter);
|
|
}
|
|
}
|
|
|
|
return new FightersEnterStaging
|
|
{
|
|
enteringAllyFighters = enteringAllyFighters,
|
|
enteringEnemyFighters = enterEnemyFighters
|
|
};
|
|
}
|
|
|
|
private void ExecuteFightersEnter()
|
|
{
|
|
Debug.Assert(_fightersEnterStaging != null);
|
|
foreach (var fighter in _fightersEnterStaging.enteringAllyFighters)
|
|
{
|
|
fighter.entered = true;
|
|
HappeningData.fighterStack.AddAsLast(fighter);
|
|
}
|
|
|
|
foreach (var fighter in _fightersEnterStaging.enteringEnemyFighters)
|
|
{
|
|
fighter.entered = true;
|
|
HappeningData.fighterStack.AddAsLast(fighter);
|
|
}
|
|
}
|
|
|
|
private void ExecuteNextFighter()
|
|
{
|
|
HappeningData.fighterStack.Next();
|
|
}
|
|
|
|
private void ExecuteAction()
|
|
{
|
|
Debug.Assert(_actionStaging != null);
|
|
_actionStaging.ExecuteAction();
|
|
}
|
|
|
|
private Variant<float, Func<bool>> GetActionAnimationEnd()
|
|
{
|
|
Debug.Assert(_actionStaging != null);
|
|
return _actionStaging.GetAnimationEnd();
|
|
}
|
|
|
|
private bool ActionAbort()
|
|
{
|
|
Debug.Assert(_actionStaging != null);
|
|
return _actionStaging.MarkedForAbort();
|
|
}
|
|
|
|
private FighterAction.FighterActionDetail? ActionNeededDetail()
|
|
{
|
|
Debug.Assert(_actionStaging != null);
|
|
return _actionStaging.NeededDetail();
|
|
}
|
|
|
|
#endregion // Game Logic
|
|
|
|
#region Utility
|
|
|
|
private void RequireState(params FightState[] states)
|
|
{
|
|
if (states.Contains(HappeningData.fightState))
|
|
return;
|
|
|
|
throw new Exception(
|
|
$"Can not call this Method while in state {HappeningData.fightState}. Only available in {string.Join(" ,", states)}");
|
|
}
|
|
|
|
private void AdvanceToStateInSeconds(FightState nextState, float seconds)
|
|
{
|
|
FightWorld.Instance.GetTree().CreateTimer(seconds).Timeout += () => ChangeState(nextState);
|
|
}
|
|
|
|
private async Task AdvanceToStateWhenDone(FightState nextState, Func<bool> isDone)
|
|
{
|
|
while (!isDone())
|
|
{
|
|
await FightWorld.Instance.ToSignal(FightWorld.Instance.GetTree(), SceneTree.SignalName.ProcessFrame);
|
|
}
|
|
|
|
ChangeState(nextState);
|
|
}
|
|
|
|
#endregion
|
|
} |