Fight happening base setup
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user