Fight happening base setup
This commit is contained in:
@@ -18,10 +18,13 @@ public partial class CameraController : Camera2D
|
||||
|
||||
[Export] private Node2D _followNode;
|
||||
|
||||
public FightInstance? fightToShow;
|
||||
public FightHappening? fightToShow;
|
||||
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
this.GlobalPosition = fightToShow?.camPositionNode.GlobalPosition ?? _followNode.GlobalPosition;
|
||||
this.GlobalPosition = /*fightToShow?.camPositionNode.GlobalPosition ??*/ _followNode.GlobalPosition;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class AllFightersVisual : Node
|
||||
{
|
||||
[Export] private Node2D _allyFighters;
|
||||
[Export] private Node2D _enemyFighters;
|
||||
|
||||
[Export] private PackedScene _blobFighterVisual;
|
||||
[Export] private PackedScene _bigBlobFighterVisual;
|
||||
[Export] private PackedScene _mavkaFighterVisual;
|
||||
[Export] private PackedScene _yourMomFighterVisual;
|
||||
[Export] private PackedScene _vesnaFighterVisual;
|
||||
|
||||
public void EnterFighter(FightWorld.Fighter fighter, bool isEnemy)
|
||||
{
|
||||
var parent = isEnemy ? _enemyFighters : _allyFighters;
|
||||
|
||||
var packedScene = fighter.type switch
|
||||
{
|
||||
FightWorld.Fighter.Type.Blob => _blobFighterVisual,
|
||||
FightWorld.Fighter.Type.BigBlob => _bigBlobFighterVisual,
|
||||
FightWorld.Fighter.Type.Mavka => _mavkaFighterVisual,
|
||||
FightWorld.Fighter.Type.YourMom => _yourMomFighterVisual,
|
||||
FightWorld.Fighter.Type.Vesna => _vesnaFighterVisual,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var fighterVisual = packedScene.Instantiate<FighterVisual>();
|
||||
fighterVisual.Initialize(fighter);
|
||||
parent.AddChild(fighterVisual);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dwsqst8fhhqlc
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightHappeningSceneSetup : Node2D
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var fightHappening = FightWorld.Instance.fightHappeningData;
|
||||
Debug.Assert(fightHappening != null, "Fight happening scene loaded, without a fight happening");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cnhpnn8o0gybd
|
||||
+1
-1
@@ -2,7 +2,7 @@ using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightSceneSetup : Node
|
||||
public partial class FightRoomSceneSetup : Node
|
||||
{
|
||||
[Export] private Label debugLabel;
|
||||
public override void _Ready()
|
||||
@@ -9,15 +9,15 @@ public partial class FightSceneSwitcher : Node
|
||||
{
|
||||
[Export] private Node sceneRoot;
|
||||
[Export] private string fightRoomScenePath;
|
||||
[Export] private string fightingGroupScene;
|
||||
[Export] private string fightHappeningScene;
|
||||
|
||||
private void LoadNext()
|
||||
{
|
||||
var nextRoom = FightWorld.Instance.currentRoom;
|
||||
Debug.Assert(nextRoom != null, "nextRoom!=null");
|
||||
var nextEnemyGroup = FightWorld.Instance.inFightWith;
|
||||
SceneTransitionThreaded.Instance.ChangeSceneToFile(nextEnemyGroup != null
|
||||
? fightingGroupScene
|
||||
var nextFightHappening = FightWorld.Instance.fightHappeningData;
|
||||
SceneTransitionThreaded.Instance.ChangeSceneToFile(nextFightHappening != null
|
||||
? fightHappeningScene
|
||||
: fightRoomScenePath);
|
||||
UnloadAfterDelay();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public static class FightUtils
|
||||
{
|
||||
public static int GetEnteredAmount(this FightWorld.EnemyGroup self)
|
||||
{
|
||||
return self.enemies.Count(e => e.IsAlive() && e.entered);
|
||||
}
|
||||
|
||||
public static bool TryGetFirstUnenteredFighter(this FightWorld.EnemyGroup self, out FightWorld.Fighter fighter)
|
||||
{
|
||||
foreach (var f in self.enemies.Where(e=>!e.entered && e.IsAlive()))
|
||||
{
|
||||
fighter = f;
|
||||
return true;
|
||||
}
|
||||
|
||||
fighter = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsAlive(this FightWorld.Fighter self)
|
||||
{
|
||||
return self.GetHealth() >= 0;
|
||||
}
|
||||
|
||||
public static bool IsDead(this FightWorld.Fighter self)
|
||||
{
|
||||
return !self.IsAlive();
|
||||
}
|
||||
|
||||
public static int GetHealth(this FightWorld.Fighter self)
|
||||
{
|
||||
return self.health ?? self.maxHealth;
|
||||
}
|
||||
|
||||
public static bool AreAllDead(this FightWorld.EnemyGroup self)
|
||||
{
|
||||
return self.enemies.All(e => e.IsDead());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://beuhpltb84pf
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Babushka.scripts.CSharp.Common.Util;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
@@ -18,21 +19,39 @@ public partial class FightWorld : Node
|
||||
|
||||
public class EnemyGroup
|
||||
{
|
||||
public required List<Enemy> enemies;
|
||||
public required List<Fighter> enemies;
|
||||
}
|
||||
|
||||
public class Enemy
|
||||
public class FightHappeningData
|
||||
{
|
||||
public FightHappening.FightState fightState = FightHappening.FightState.None;
|
||||
public FighterStack fighterStack = new();
|
||||
public required EnemyGroup enemyGroup;
|
||||
}
|
||||
|
||||
public class Fighter
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Blob,
|
||||
BigBlob,
|
||||
Mavka,
|
||||
YourMom
|
||||
YourMom,
|
||||
Vesna
|
||||
}
|
||||
|
||||
public required Type type;
|
||||
public required int? health = null; // null => initialize to full health on spawn
|
||||
public required int maxHealth;
|
||||
public required bool isEnemy;
|
||||
public required List<FighterAction> availableActions;
|
||||
public int? health = null; // null => initialize to full health on spawn
|
||||
public bool entered = false;
|
||||
public int actionsLeft;
|
||||
|
||||
public FighterAction AutoSelectAction()
|
||||
{
|
||||
return availableActions.Random() ?? new FighterAction.Skip();
|
||||
}
|
||||
}
|
||||
|
||||
#region AutoLoad ( Contains _EnterTree() )
|
||||
@@ -46,10 +65,10 @@ public partial class FightWorld : Node
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public World? world = null;
|
||||
public Room? currentRoom = null;
|
||||
public EnemyGroup? inFightWith = null;
|
||||
public FightHappeningData? fightHappeningData = null;
|
||||
|
||||
public void MyEnterTree()
|
||||
{
|
||||
@@ -135,22 +154,27 @@ public partial class FightWorld : Node
|
||||
return enemyGroup;
|
||||
}
|
||||
|
||||
private Enemy GenerateSingleEnemy()
|
||||
private Fighter GenerateSingleEnemy()
|
||||
{
|
||||
var typeRoll = GD.RandRange(0, 99);
|
||||
|
||||
var type = typeRoll switch
|
||||
{
|
||||
< 50 => Enemy.Type.Blob,
|
||||
< 75 => Enemy.Type.BigBlob,
|
||||
< 90 => Enemy.Type.Mavka,
|
||||
_ => Enemy.Type.YourMom
|
||||
< 50 => Fighter.Type.Blob,
|
||||
< 75 => Fighter.Type.BigBlob,
|
||||
< 90 => Fighter.Type.Mavka,
|
||||
_ => Fighter.Type.YourMom
|
||||
};
|
||||
|
||||
var enemy = new Enemy
|
||||
var enemy = new Fighter
|
||||
{
|
||||
type = type,
|
||||
health = null
|
||||
health = null,
|
||||
isEnemy = true,
|
||||
maxHealth = 12,
|
||||
availableActions = [
|
||||
new FighterAction.Skip()
|
||||
]
|
||||
};
|
||||
|
||||
return enemy;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Babushka.scripts.CSharp.Common.Util;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public abstract class FighterAction
|
||||
{
|
||||
public class TargetSelection
|
||||
{
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
public static readonly TargetSelection Skip = new() { skipTargetSelection = () => true };
|
||||
public Func<bool> skipTargetSelection = () => false;
|
||||
}
|
||||
|
||||
public abstract class FighterActionDetail
|
||||
{
|
||||
public abstract bool DetailComplete();
|
||||
}
|
||||
|
||||
private bool _abort = false;
|
||||
|
||||
#region Shortcuts
|
||||
|
||||
protected static FightWorld.FightHappeningData HappeningData =>
|
||||
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Executes the data modification for the action. This must happen instantly and not via a coroutines or callbacks.
|
||||
/// To get a multiple hit effect for one action, use appropriate Visual Manipulation functions in AnimateAction.
|
||||
/// </summary>
|
||||
public virtual void ExecuteAction()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a way to determine, when an action animation is done
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A variant that can be <c>float</c> or <c>Func<bool></c>.<br/>
|
||||
/// When the return type is <c>float</c>, the animation will take the return value amount of seconds.<br/>
|
||||
/// When the return type is <c>Func<bool></c>, the animation will be done, when the function returns true.
|
||||
/// </returns>
|
||||
public abstract Variant<float, Func<bool>> GetAnimationEnd();
|
||||
|
||||
/// <summary>
|
||||
/// Animates the action.
|
||||
/// </summary>
|
||||
public virtual async Task AnimateAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void MarkAbort()
|
||||
{
|
||||
_abort = true;
|
||||
}
|
||||
|
||||
public bool MarkedForAbort()
|
||||
{
|
||||
return _abort;
|
||||
}
|
||||
|
||||
public abstract FighterActionDetail? NeededDetail();
|
||||
|
||||
public class Skip : FighterAction
|
||||
{
|
||||
public override Variant<float, Func<bool>> GetAnimationEnd()
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public override FighterActionDetail? NeededDetail()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot.NativeInterop;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public class FighterStack
|
||||
{
|
||||
private class Node
|
||||
{
|
||||
public Node next;
|
||||
public FightWorld.Fighter fighter;
|
||||
}
|
||||
|
||||
private Node? currentNode;
|
||||
|
||||
public FightWorld.Fighter Current => currentNode.fighter;
|
||||
|
||||
public void Next()
|
||||
{
|
||||
currentNode = currentNode.next;
|
||||
}
|
||||
|
||||
public FightWorld.Fighter PeekNext()
|
||||
{
|
||||
return currentNode.next.fighter;
|
||||
}
|
||||
|
||||
public void AddAsLast(FightWorld.Fighter value)
|
||||
{
|
||||
// if first node
|
||||
if (currentNode == null)
|
||||
{
|
||||
currentNode = new Node { fighter = value };
|
||||
currentNode.next = currentNode;
|
||||
return;
|
||||
}
|
||||
|
||||
var newNode = new Node { fighter = value, next = currentNode };
|
||||
var node = currentNode;
|
||||
while (node.next != currentNode)
|
||||
{
|
||||
node = node.next;
|
||||
}
|
||||
|
||||
node.next = newNode;
|
||||
}
|
||||
|
||||
public void AddAsNext(FightWorld.Fighter value)
|
||||
{
|
||||
// if first node
|
||||
if (currentNode == null)
|
||||
{
|
||||
AddAsLast(value);
|
||||
return;
|
||||
}
|
||||
|
||||
var newNode = new Node { fighter = value, next = currentNode.next };
|
||||
currentNode.next = newNode;
|
||||
}
|
||||
|
||||
public bool Remove(FightWorld.Fighter value)
|
||||
{
|
||||
if (currentNode == null) return false;
|
||||
|
||||
// if only one node
|
||||
if (currentNode.next == currentNode)
|
||||
{
|
||||
if (currentNode.fighter == value)
|
||||
{
|
||||
currentNode = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var node = currentNode;
|
||||
do
|
||||
{
|
||||
// next is the fighter to remove
|
||||
if (node.next.fighter == value)
|
||||
{
|
||||
// if removing current, keep current
|
||||
// it will be implicitly deleted on the next Next() call
|
||||
|
||||
node.next = node.next.next;
|
||||
return true;
|
||||
}
|
||||
|
||||
node = node.next;
|
||||
} while (node != currentNode);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+36
-60
@@ -1,12 +1,13 @@
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class Fighter : Node2D
|
||||
public partial class FighterVisual : Node2D
|
||||
{
|
||||
[Export] public string name;
|
||||
[Export] public int maxHealth;
|
||||
[Export] public int attackStrength;
|
||||
[Export] public int maxActions = 1;
|
||||
//[Export] public string name;
|
||||
//[Export] public int maxHealth;
|
||||
//[Export] public int attackStrength;
|
||||
//[Export] public int maxActions = 1;
|
||||
[Export] public FightWorld.Fighter.Type type;
|
||||
|
||||
[ExportCategory("References")]
|
||||
[Export] private Node2D _attackButtons;
|
||||
@@ -21,45 +22,29 @@ public partial class Fighter : Node2D
|
||||
[Signal] public delegate void HealedEventHandler();
|
||||
|
||||
|
||||
private int _health;
|
||||
private int _actions;
|
||||
private FightWorld.Fighter _boundFighter;
|
||||
|
||||
//private void Die()
|
||||
//{
|
||||
// _visualSprite.Scale = new Vector2(1, 0.3f);
|
||||
// EmitSignalDying();
|
||||
//}
|
||||
|
||||
public FightInstance fightInstance;
|
||||
public int Health
|
||||
//public override void _Ready()
|
||||
//{
|
||||
// UpdateHealthVisual();
|
||||
// ResetActions();
|
||||
//}
|
||||
|
||||
public void Initialize(FightWorld.Fighter fighter)
|
||||
{
|
||||
get => _health;
|
||||
set
|
||||
{
|
||||
_health = value;
|
||||
if (_health <= 0)
|
||||
{
|
||||
_health = 0;
|
||||
Die();
|
||||
}
|
||||
if (_health > maxHealth)
|
||||
{
|
||||
_health = maxHealth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Die()
|
||||
{
|
||||
_visualSprite.Scale = new Vector2(1, 0.3f);
|
||||
EmitSignalDying();
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Health = maxHealth;
|
||||
_boundFighter = fighter;
|
||||
UpdateHealthVisual();
|
||||
ResetActions();
|
||||
}
|
||||
|
||||
public void Attack()
|
||||
{
|
||||
fightInstance.SelectAttack(this);
|
||||
//FightHappening.SelectAttack(this);
|
||||
}
|
||||
|
||||
public void HideAttackButton()
|
||||
@@ -102,17 +87,17 @@ public partial class Fighter : Node2D
|
||||
|
||||
private void ClickedAttack()
|
||||
{
|
||||
fightInstance.SelectAttack(this);
|
||||
//FightHappening.SelectAttack(this);
|
||||
}
|
||||
|
||||
private void ClickedHeal()
|
||||
{
|
||||
fightInstance.SelectHeal(this);
|
||||
//FightHappening.SelectHeal(this);
|
||||
}
|
||||
|
||||
private void ClickedTarget()
|
||||
{
|
||||
fightInstance.SelectTargetAndAttack(this);
|
||||
//FightHappening.SelectTargetAndAttack(this);
|
||||
}
|
||||
|
||||
public void StartHoverTarget()
|
||||
@@ -127,7 +112,18 @@ public partial class Fighter : Node2D
|
||||
|
||||
public void UpdateHealthVisual()
|
||||
{
|
||||
_healthText.Text = $"{Health}/{maxHealth}";
|
||||
_healthText.Text = $"{_boundFighter.health}";
|
||||
}
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
//return Health <= 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResetActions()
|
||||
{
|
||||
//_actions = maxActions;
|
||||
}
|
||||
|
||||
public void AttackAnimation(FightAttack attack)
|
||||
@@ -150,26 +146,6 @@ public partial class Fighter : Node2D
|
||||
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
|
||||
}
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
return Health <= 0;
|
||||
}
|
||||
|
||||
public void ResetActions()
|
||||
{
|
||||
_actions = maxActions;
|
||||
}
|
||||
|
||||
public bool HasActionsLeft()
|
||||
{
|
||||
return _actions > 0;
|
||||
}
|
||||
|
||||
public void DecrementActions()
|
||||
{
|
||||
_actions--;
|
||||
}
|
||||
|
||||
public void HealAnimation()
|
||||
{
|
||||
EmitSignalHealed();
|
||||
@@ -0,0 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public class NoFightHappeningException() : Exception("No fight happening right now")
|
||||
{
|
||||
}
|
||||
@@ -4,6 +4,6 @@ public class FightAttack
|
||||
{
|
||||
public int damage;
|
||||
public bool needsSelectedTarget;
|
||||
public Fighter? target;
|
||||
public Fighter attacker;
|
||||
public FighterVisual? target;
|
||||
public FighterVisual attacker;
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Babushka.scripts.CSharp.Common.Camera;
|
||||
using Babushka.scripts.CSharp.Common.Util;
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightInstance : Node2D //TODO: remake
|
||||
{
|
||||
[Export(PropertyHint.ArrayType)] private Node2D[] _friendlyFightSpots;
|
||||
[Export(PropertyHint.ArrayType)] private Node2D[] _enemyFightSpots;
|
||||
[Export] public Node2D camPositionNode;
|
||||
[Export] private FightStateManager _fightStateManager;
|
||||
[Export] private Label _fightEndText;
|
||||
|
||||
|
||||
[Signal]
|
||||
public delegate void FightStartedEventHandler();
|
||||
|
||||
[Signal]
|
||||
public delegate void FightEndedEventHandler();
|
||||
|
||||
private List<Fighter> _friendlyFighters = new();
|
||||
private List<Fighter> _enemyFighters = new();
|
||||
|
||||
private FightAttack? _stagedAttack = null;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
//_fightStateManager.CurrentFightState = FightStateManager.FightState.FightStartAnim;
|
||||
_fightStateManager.ExitingTransition += from =>
|
||||
{
|
||||
switch (from)
|
||||
{
|
||||
case FightStateManager.FightState.None:
|
||||
CaptureCamera();
|
||||
Show();
|
||||
EmitSignalFightStarted();
|
||||
break;
|
||||
case FightStateManager.FightState.Input:
|
||||
HideAttackButtons();
|
||||
break;
|
||||
case FightStateManager.FightState.InputTargetSelect:
|
||||
HideTargetButtons();
|
||||
break;
|
||||
case FightStateManager.FightState.FriendAttackAnim:
|
||||
_stagedAttack = null;
|
||||
break;
|
||||
case FightStateManager.FightState.PlayerWinAnim:
|
||||
case FightStateManager.FightState.EnemyWinAnim:
|
||||
_fightEndText.Text = "";
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
_fightStateManager.EnteringTransition += to =>
|
||||
{
|
||||
switch (to)
|
||||
{
|
||||
case FightStateManager.FightState.None:
|
||||
EmitSignalFightEnded();
|
||||
CleanUp();
|
||||
Hide();
|
||||
ReleaseCamera();
|
||||
break;
|
||||
case FightStateManager.FightState.Input:
|
||||
if (CheckWinAndSetState())
|
||||
break;
|
||||
if (CheckFriendlyActionsLeftAndSetState())
|
||||
break;
|
||||
ShowAttackButtons();
|
||||
break;
|
||||
case FightStateManager.FightState.InputTargetSelect:
|
||||
ShowTargetButtons();
|
||||
break;
|
||||
case FightStateManager.FightState.FriendAttackAnim:
|
||||
ExecuteAttack();
|
||||
GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input;
|
||||
break;
|
||||
case FightStateManager.FightState.Enemy:
|
||||
if (CheckWinAndSetState())
|
||||
break;
|
||||
if (CheckEnemyActionsLeftAndSetState())
|
||||
break;
|
||||
DecideEnemyAttack();
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyAttackAnim;
|
||||
break;
|
||||
case FightStateManager.FightState.EnemyAttackAnim:
|
||||
ExecuteAttack();
|
||||
GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy;
|
||||
break;
|
||||
case FightStateManager.FightState.PlayerWinAnim:
|
||||
_fightEndText.Text = "You Win!";
|
||||
GetTree().CreateTimer(1.5).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.None;
|
||||
break;
|
||||
case FightStateManager.FightState.EnemyWinAnim:
|
||||
_fightEndText.Text = "You Died :(";
|
||||
GetTree().CreateTimer(3).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.None;
|
||||
break;
|
||||
case FightStateManager.FightState.ChangeSideToEnemy:
|
||||
ResetEnemyActions();
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy;
|
||||
break;
|
||||
case FightStateManager.FightState.ChangeSideToFriendly:
|
||||
ResetFriendlyActions();
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.Input;
|
||||
break;
|
||||
case FightStateManager.FightState.Heal:
|
||||
Heal();
|
||||
GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
private void Heal()
|
||||
{
|
||||
// TODO: make heal staging system
|
||||
_friendlyFighters.Where(f => !f.IsDead()).ForEach(f =>
|
||||
{
|
||||
f.Health += 50;
|
||||
f.HealAnimation();
|
||||
f.DecrementActions();
|
||||
});
|
||||
UpdateHealthVisual();
|
||||
}
|
||||
private void ResetEnemyActions()
|
||||
{
|
||||
_enemyFighters.ForEach(f => f.ResetActions());
|
||||
}
|
||||
|
||||
private void ResetFriendlyActions()
|
||||
{
|
||||
_friendlyFighters.ForEach(f => f.ResetActions());
|
||||
}
|
||||
|
||||
/**
|
||||
* <returns>
|
||||
* <c>true</c> if the state was changed
|
||||
* </returns>
|
||||
*/
|
||||
private bool CheckFriendlyActionsLeftAndSetState()
|
||||
{
|
||||
var hasActionsLeft = _friendlyFighters.Where(f => !f.IsDead()).Any(f => f.HasActionsLeft());
|
||||
if (hasActionsLeft)
|
||||
{
|
||||
return false;
|
||||
} // else
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.ChangeSideToEnemy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <returns>
|
||||
* <c>true</c> if the state was changed
|
||||
* </returns>
|
||||
*/
|
||||
private bool CheckEnemyActionsLeftAndSetState()
|
||||
{
|
||||
var hasActionsLeft = _enemyFighters.Where(f => !f.IsDead()).Any(f => f.HasActionsLeft());
|
||||
if (hasActionsLeft)
|
||||
{
|
||||
return false;
|
||||
} // else
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.ChangeSideToFriendly;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CleanUp()
|
||||
{
|
||||
_enemyFighters.ForEach(f => f.QueueFree());
|
||||
_friendlyFighters.ForEach(f => f.QueueFree());
|
||||
_enemyFighters = new();
|
||||
_friendlyFighters = new();
|
||||
}
|
||||
private void DecideEnemyAttack()
|
||||
{
|
||||
var availableEnemyFighters =
|
||||
_enemyFighters
|
||||
.Where(f => !f.IsDead())
|
||||
.Where(f=>f.HasActionsLeft())
|
||||
.ToList();
|
||||
var aliveFriendlyFighters =
|
||||
_friendlyFighters
|
||||
.Where(f => !f.IsDead())
|
||||
.ToList();
|
||||
|
||||
if (availableEnemyFighters.Count <= 0)
|
||||
throw new InvalidOperationException("No enemy fighters available for attack.");
|
||||
|
||||
if (aliveFriendlyFighters.Count <= 0)
|
||||
throw new InvalidOperationException("No friendly fighters available to target.");
|
||||
|
||||
var fighter = availableEnemyFighters.Random();
|
||||
var target = aliveFriendlyFighters.Random();
|
||||
|
||||
_stagedAttack = new FightAttack
|
||||
{
|
||||
attacker = fighter!,
|
||||
needsSelectedTarget = true,
|
||||
damage = fighter!.attackStrength,
|
||||
target = target!
|
||||
};
|
||||
}
|
||||
|
||||
private void ExecuteAttack()
|
||||
{
|
||||
if (_stagedAttack == null)
|
||||
throw new InvalidOperationException("No staged attack to execute.");
|
||||
|
||||
if (!_stagedAttack.needsSelectedTarget)
|
||||
throw new NotImplementedException("Non-targeted attacks are not implemented yet.");
|
||||
|
||||
if (_stagedAttack.needsSelectedTarget && _stagedAttack.target == null)
|
||||
throw new InvalidOperationException("No target selected for the staged attack.");
|
||||
|
||||
_stagedAttack.target!.Health -= _stagedAttack.damage;
|
||||
_stagedAttack.attacker.DecrementActions();
|
||||
_stagedAttack.attacker.AttackAnimation(_stagedAttack);
|
||||
|
||||
UpdateHealthVisual();
|
||||
}
|
||||
|
||||
private void UpdateHealthVisual()
|
||||
{
|
||||
_friendlyFighters
|
||||
.Concat(_enemyFighters)
|
||||
.ForEach(f => f.UpdateHealthVisual());
|
||||
}
|
||||
|
||||
private void ReleaseCamera()
|
||||
{
|
||||
CameraController.Instance.fightToShow = null;
|
||||
}
|
||||
|
||||
private void CaptureCamera()
|
||||
{
|
||||
CameraController.Instance.fightToShow = this;
|
||||
}
|
||||
|
||||
public void Start(FightParty fightParty, PackedScene?[] enemies)
|
||||
{
|
||||
if (_fightStateManager.IsRunning())
|
||||
{
|
||||
GD.PushWarning("Can not start a running fight");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fightParty.vesna)
|
||||
{
|
||||
InstantiateFighter(_friendlyFightSpots[1], FightManager.Instance.fightingVesnaScene);
|
||||
}
|
||||
|
||||
for (var i = 0; i < Math.Min(_enemyFightSpots.Length, enemies.Length); i++)
|
||||
{
|
||||
var enemy = enemies[i];
|
||||
if (enemy == null)
|
||||
continue;
|
||||
|
||||
InstantiateFighter(_enemyFightSpots[i], enemy, true);
|
||||
}
|
||||
|
||||
_fightStateManager.ToStartAnim();
|
||||
}
|
||||
|
||||
private void InstantiateFighter(Node2D parent, PackedScene fighterScene, bool isEnemy = false)
|
||||
{
|
||||
var fighter = fighterScene.Instantiate<Fighter>();
|
||||
fighter.fightInstance = this;
|
||||
parent.AddChild(fighter);
|
||||
|
||||
if (isEnemy)
|
||||
{
|
||||
_enemyFighters.Add(fighter);
|
||||
}
|
||||
else
|
||||
{
|
||||
_friendlyFighters.Add(fighter);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectAttack(Fighter fighter)
|
||||
{
|
||||
_stagedAttack = new FightAttack
|
||||
{
|
||||
attacker = fighter,
|
||||
damage = fighter.attackStrength,
|
||||
needsSelectedTarget = true
|
||||
};
|
||||
|
||||
if (_stagedAttack.needsSelectedTarget)
|
||||
{
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.InputTargetSelect;
|
||||
}
|
||||
else
|
||||
{
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.FriendAttackAnim;
|
||||
}
|
||||
}
|
||||
|
||||
private void HideAttackButtons()
|
||||
{
|
||||
_friendlyFighters.ForEach(f => f.HideAttackButton());
|
||||
}
|
||||
|
||||
private void ShowAttackButtons()
|
||||
{
|
||||
_friendlyFighters.ForEach(f => f.ShowAttackButton());
|
||||
}
|
||||
|
||||
private void HideTargetButtons()
|
||||
{
|
||||
_enemyFighters.ForEach(f => f.HideTargetButtons());
|
||||
}
|
||||
|
||||
private void ShowTargetButtons()
|
||||
{
|
||||
_enemyFighters.Where(f => !f.IsDead()).ForEach(f => f.ShowTargetButtons());
|
||||
}
|
||||
|
||||
public void SelectTargetAndAttack(Fighter fighter)
|
||||
{
|
||||
if (_stagedAttack == null)
|
||||
throw new InvalidOperationException("No staged attack to select target for.");
|
||||
|
||||
_stagedAttack.target = fighter;
|
||||
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.FriendAttackAnim;
|
||||
}
|
||||
|
||||
public void SelectHeal(Fighter fighter)
|
||||
{
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.Heal;
|
||||
}
|
||||
|
||||
public bool CheckWinAndSetState()
|
||||
{
|
||||
if (_enemyFighters.All(f => f.IsDead()))
|
||||
{
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.PlayerWinAnim;
|
||||
return true;
|
||||
}
|
||||
if (_friendlyFighters.All(f => f.IsDead()))
|
||||
{
|
||||
_fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyWinAnim;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ public partial class FightManager : Node
|
||||
|
||||
public FightParty fightParty = new();
|
||||
|
||||
public void StartFight(PackedScene[] enemies, FightInstance instance)
|
||||
public void StartFight(PackedScene[] enemies, FightHappening happening)
|
||||
{
|
||||
GD.Print("Starting Fight");
|
||||
instance.Start(fightParty, enemies);
|
||||
//happening.Start(fightParty, enemies);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
public partial class FightStarter : Node
|
||||
{
|
||||
[Export(PropertyHint.ArrayType)] private PackedScene[] enemies;
|
||||
[Export] private FightInstance _fightInstance;
|
||||
//[Export] private FightHappening _fightHappening;
|
||||
[Export] private bool _once = true;
|
||||
private bool hasBeenStarted = false;
|
||||
|
||||
@@ -14,6 +14,6 @@ public partial class FightStarter : Node
|
||||
return;
|
||||
|
||||
hasBeenStarted = true;
|
||||
FightManager.Instance.StartFight(enemies, _fightInstance);
|
||||
//FightManager.Instance.StartFight(enemies, _fightHappening);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightStateManager : Node
|
||||
{
|
||||
[Signal]
|
||||
public delegate void ExitingTransitionEventHandler(FightState exitingState);
|
||||
|
||||
[Signal]
|
||||
public delegate void EnteringTransitionEventHandler(FightState enteringState);
|
||||
|
||||
public enum FightState
|
||||
{
|
||||
None,
|
||||
FightStartAnim,
|
||||
Input,
|
||||
InputTargetSelect,
|
||||
FriendAttackAnim,
|
||||
Enemy,
|
||||
EnemyAttackAnim,
|
||||
PlayerWinAnim,
|
||||
EnemyWinAnim,
|
||||
ChangeSideToEnemy,
|
||||
ChangeSideToFriendly,
|
||||
Heal,
|
||||
}
|
||||
|
||||
private FightState _fightStateBacking = FightState.None;
|
||||
|
||||
public FightState CurrentFightState
|
||||
{
|
||||
set => Transition(_fightStateBacking, value);
|
||||
get => _fightStateBacking;
|
||||
}
|
||||
|
||||
private void Transition(FightState from, FightState to)
|
||||
{
|
||||
if(from == to)
|
||||
return;
|
||||
|
||||
GD.Print($"Transitioning from {from} to {to}");
|
||||
ExitTransition(from);
|
||||
_fightStateBacking = to;
|
||||
EnterTransition(to);
|
||||
}
|
||||
|
||||
private void ExitTransition(FightState from)
|
||||
{
|
||||
EmitSignalExitingTransition(from);
|
||||
}
|
||||
|
||||
private void EnterTransition(FightState to)
|
||||
{
|
||||
EmitSignalEnteringTransition(to);
|
||||
switch (to)
|
||||
{
|
||||
case FightState.FightStartAnim:
|
||||
EnterFightStartAnim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void EnterFightStartAnim()
|
||||
{
|
||||
GetTree().CreateTimer(1).Timeout += () => CurrentFightState = FightState.Input;
|
||||
}
|
||||
|
||||
public void ToStartAnim()
|
||||
{
|
||||
CurrentFightState = FightState.FightStartAnim;
|
||||
}
|
||||
|
||||
public bool IsRunning()
|
||||
{
|
||||
return CurrentFightState != FightState.None;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://oe1uypehqvr7
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Util;
|
||||
|
||||
public class Variant<T1, T2>
|
||||
{
|
||||
private Type _type;
|
||||
private Object _value;
|
||||
|
||||
public Type GetVariantType()
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
public T GetValue<T>()
|
||||
{
|
||||
if (_type != typeof(T))
|
||||
throw new ArgumentOutOfRangeException(
|
||||
$"The Variant does not store a {typeof(T)}. The current type is {_type}");
|
||||
|
||||
return (T)_value;
|
||||
}
|
||||
|
||||
public void SetValue<T>(T value)
|
||||
{
|
||||
if (typeof(T1) != typeof(T) && typeof(T2) != typeof(T))
|
||||
throw new ArgumentOutOfRangeException(
|
||||
$"The Variant does not support type {typeof(T)}. Supported types are {typeof(T1)} and {typeof(T2)}");
|
||||
|
||||
_type = typeof(T);
|
||||
_value = value!;
|
||||
}
|
||||
|
||||
public static implicit operator T1(Variant<T1, T2> v)
|
||||
{
|
||||
return v.GetValue<T1>();
|
||||
}
|
||||
|
||||
public static implicit operator T2(Variant<T1, T2> v)
|
||||
{
|
||||
return v.GetValue<T2>();
|
||||
}
|
||||
|
||||
public static implicit operator Variant<T1, T2>(T1 t)
|
||||
{
|
||||
return new Variant<T1, T2> { _type = typeof(T1), _value = t! };
|
||||
}
|
||||
|
||||
public static implicit operator Variant<T1, T2>(T2 t)
|
||||
{
|
||||
return new Variant<T1, T2> { _type = typeof(T2), _value = t! };
|
||||
}
|
||||
|
||||
public bool IsType<T>()
|
||||
{
|
||||
if (typeof(T1) != typeof(T) && typeof(T2) != typeof(T))
|
||||
throw new ArgumentOutOfRangeException(
|
||||
$"The Variant does not support type {typeof(T)}. Supported types are {typeof(T1)} and {typeof(T2)}");
|
||||
|
||||
return _type == typeof(T);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user