Bootstrap fight system
- Fight World data structure - Generating basic fight world - Opening correct fight room - Block paths in fight rooms - Transition between rooms
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public class FightAttack
|
||||
{
|
||||
public int damage;
|
||||
public bool needsSelectedTarget;
|
||||
public Fighter? target;
|
||||
public Fighter attacker;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://cnggo5jyimosu
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://c76mhhqyk4lgh
|
||||
@@ -1,28 +0,0 @@
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightManager : Node
|
||||
{
|
||||
#region AutoLoad ( Contains _EnterTree() )
|
||||
|
||||
public static FightManager Instance { get; private set; } = null!;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Export]
|
||||
public PackedScene fightingVesnaScene;
|
||||
|
||||
public FightParty fightParty = new();
|
||||
|
||||
public void StartFight(PackedScene[] enemies, FightInstance instance)
|
||||
{
|
||||
GD.Print("Starting Fight");
|
||||
instance.Start(fightParty, enemies);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://j5ge24rk25wm
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public class FightParty
|
||||
{
|
||||
public bool vesna = true;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://cvhgnboybc4cm
|
||||
@@ -0,0 +1,21 @@
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightSceneSetup : Node
|
||||
{
|
||||
[Export] private Label debugLabel;
|
||||
public override void _Ready()
|
||||
{
|
||||
var room = FightWorld.Instance.currentRoom!;
|
||||
debugLabel.Text = $"Room Debug:\n{room.paths.Count} paths out of this room\n{room.enemyGroups.Count} enemy groups:\n";
|
||||
foreach (var enemyGroup in room.enemyGroups)
|
||||
{
|
||||
debugLabel.Text += $" {enemyGroup.enemies.Count} enemies:\n";
|
||||
foreach (var enemy in enemyGroup.enemies)
|
||||
{
|
||||
debugLabel.Text += $" {enemy.type}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbu8afaiohpdh
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Babushka.scripts.CSharp.Common.SceneManagement;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightSceneSwitcher : Node
|
||||
{
|
||||
[Export] private Node sceneRoot;
|
||||
[Export] private string fightRoomScenePath;
|
||||
[Export] private string fightingGroupScene;
|
||||
|
||||
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
|
||||
: fightRoomScenePath);
|
||||
UnloadAfterDelay();
|
||||
}
|
||||
|
||||
private async void UnloadAfterDelay()
|
||||
{
|
||||
await ToSignal(GetTree().CreateTimer(1.0f), "timeout"); // 1.0f seconds
|
||||
sceneRoot.QueueFree();
|
||||
}
|
||||
|
||||
public void SwitchRoom(int pathIndex)
|
||||
{
|
||||
Debug.Assert(FightWorld.Instance.currentRoom != null, "FightWorld.Instance.currentRoom!=null");
|
||||
|
||||
if (!FightWorld.Instance.currentRoom.paths.TryGetValue(pathIndex, out var nextRoom))
|
||||
throw new Exception("Trying to go down a non-existent path");
|
||||
|
||||
FightWorld.Instance.currentRoom = nextRoom;
|
||||
LoadNext();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cql8mt5jsmcdl
|
||||
@@ -1,19 +0,0 @@
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightStarter : Node
|
||||
{
|
||||
[Export(PropertyHint.ArrayType)] private PackedScene[] enemies;
|
||||
[Export] private FightInstance _fightInstance;
|
||||
[Export] private bool _once = true;
|
||||
private bool hasBeenStarted = false;
|
||||
|
||||
public void Start(Node2D _)
|
||||
{
|
||||
if (_once && hasBeenStarted)
|
||||
return;
|
||||
|
||||
hasBeenStarted = true;
|
||||
FightManager.Instance.StartFight(enemies, _fightInstance);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://di0xxwfw43m0i
|
||||
@@ -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,159 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class FightWorld : Node
|
||||
{
|
||||
public class World
|
||||
{
|
||||
public required List<Room> rooms;
|
||||
}
|
||||
|
||||
public class Room
|
||||
{
|
||||
public required Dictionary<int, Room> paths;
|
||||
public required List<EnemyGroup> enemyGroups;
|
||||
}
|
||||
|
||||
public class EnemyGroup
|
||||
{
|
||||
public required List<Enemy> enemies;
|
||||
}
|
||||
|
||||
public class Enemy
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Blob,
|
||||
BigBlob,
|
||||
Mavka,
|
||||
YourMom
|
||||
}
|
||||
|
||||
public required Type type;
|
||||
public required int? health = null; // null => initialize to full health on spawn
|
||||
}
|
||||
|
||||
#region AutoLoad ( Contains _EnterTree() )
|
||||
|
||||
public static FightWorld Instance { get; private set; } = null!;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
Instance = this;
|
||||
MyEnterTree();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public World? world = null;
|
||||
public Room? currentRoom = null;
|
||||
public EnemyGroup? inFightWith = null;
|
||||
|
||||
public void MyEnterTree()
|
||||
{
|
||||
Generate();
|
||||
currentRoom = world!.rooms[0];
|
||||
}
|
||||
|
||||
public void Generate()
|
||||
{
|
||||
world = new Generator().GenerateWorld();
|
||||
}
|
||||
|
||||
private class Generator
|
||||
{
|
||||
public World GenerateWorld()
|
||||
{
|
||||
var world = new World
|
||||
{
|
||||
rooms = GenerateRooms()
|
||||
};
|
||||
return world;
|
||||
}
|
||||
|
||||
private List<Room> GenerateRooms()
|
||||
{
|
||||
var rooms = new List<Room>();
|
||||
|
||||
var roomCount = 2;
|
||||
|
||||
for (var i = 0; i < roomCount; i++)
|
||||
{
|
||||
rooms.Add(GenerateDisconnectedRoom());
|
||||
}
|
||||
|
||||
// Connect rooms linearly
|
||||
for (var i = 0; i < rooms.Count - 1; i++)
|
||||
{
|
||||
rooms[i].paths[0] = rooms[i + 1];
|
||||
rooms[i + 1].paths[1] = rooms[i];
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
private Room GenerateDisconnectedRoom()
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
paths = new Dictionary<int, Room>(),
|
||||
enemyGroups = GenerateEnemyGroups()
|
||||
};
|
||||
return room;
|
||||
}
|
||||
|
||||
private List<EnemyGroup> GenerateEnemyGroups()
|
||||
{
|
||||
var enemyGroups = new List<EnemyGroup>();
|
||||
|
||||
var enemyGroupCount = GD.RandRange(1, 3);
|
||||
|
||||
for (var i = 0; i < enemyGroupCount; i++)
|
||||
{
|
||||
enemyGroups.Add(GenerateSingleEnemyGroup());
|
||||
}
|
||||
|
||||
return enemyGroups;
|
||||
}
|
||||
|
||||
private EnemyGroup GenerateSingleEnemyGroup()
|
||||
{
|
||||
var enemyGroup = new EnemyGroup
|
||||
{
|
||||
enemies = []
|
||||
};
|
||||
|
||||
var enemyCount = GD.RandRange(1, 3);
|
||||
|
||||
for (var i = 0; i < enemyCount; i++)
|
||||
{
|
||||
enemyGroup.enemies.Add(GenerateSingleEnemy());
|
||||
}
|
||||
|
||||
return enemyGroup;
|
||||
}
|
||||
|
||||
private Enemy 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
|
||||
};
|
||||
|
||||
var enemy = new Enemy
|
||||
{
|
||||
type = type,
|
||||
health = null
|
||||
};
|
||||
|
||||
return enemy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dqe1i2qmpttwf
|
||||
@@ -1,181 +0,0 @@
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class Fighter : Node2D
|
||||
{
|
||||
[Export] public string name;
|
||||
[Export] public int maxHealth;
|
||||
[Export] public int attackStrength;
|
||||
[Export] public int maxActions = 1;
|
||||
|
||||
[ExportCategory("References")]
|
||||
[Export] private Node2D _attackButtons;
|
||||
[Export] private Node2D _targetButtons;
|
||||
[Export] private Node2D _targetMarker;
|
||||
[Export] private Label _healthText;
|
||||
[Export] private Node2D _visualSprite;
|
||||
|
||||
[Signal] public delegate void DamageTakenEventHandler();
|
||||
[Signal] public delegate void AttackingEventHandler();
|
||||
[Signal] public delegate void DyingEventHandler();
|
||||
[Signal] public delegate void HealedEventHandler();
|
||||
|
||||
|
||||
private int _health;
|
||||
private int _actions;
|
||||
|
||||
|
||||
public FightInstance fightInstance;
|
||||
public int Health
|
||||
{
|
||||
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;
|
||||
UpdateHealthVisual();
|
||||
ResetActions();
|
||||
}
|
||||
|
||||
public void Attack()
|
||||
{
|
||||
fightInstance.SelectAttack(this);
|
||||
}
|
||||
|
||||
public void HideAttackButton()
|
||||
{
|
||||
_attackButtons.Hide();
|
||||
}
|
||||
|
||||
public void ShowAttackButton()
|
||||
{
|
||||
_attackButtons.Show();
|
||||
}
|
||||
|
||||
public void HideTargetButtons()
|
||||
{
|
||||
_targetButtons.Hide();
|
||||
}
|
||||
|
||||
public void ShowTargetButtons()
|
||||
{
|
||||
_targetButtons.Show();
|
||||
}
|
||||
|
||||
public void TargetMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx)
|
||||
{
|
||||
if (inputEvent.IsPressed())
|
||||
ClickedTarget();
|
||||
}
|
||||
|
||||
public void AttackMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx)
|
||||
{
|
||||
if (inputEvent.IsPressed())
|
||||
ClickedAttack();
|
||||
}
|
||||
|
||||
public void HealMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx)
|
||||
{
|
||||
if (inputEvent.IsPressed())
|
||||
ClickedHeal();
|
||||
}
|
||||
|
||||
private void ClickedAttack()
|
||||
{
|
||||
fightInstance.SelectAttack(this);
|
||||
}
|
||||
|
||||
private void ClickedHeal()
|
||||
{
|
||||
fightInstance.SelectHeal(this);
|
||||
}
|
||||
|
||||
private void ClickedTarget()
|
||||
{
|
||||
fightInstance.SelectTargetAndAttack(this);
|
||||
}
|
||||
|
||||
public void StartHoverTarget()
|
||||
{
|
||||
_targetMarker.Visible = true;
|
||||
}
|
||||
|
||||
public void EndHoverTarget()
|
||||
{
|
||||
_targetMarker.Visible = false;
|
||||
}
|
||||
|
||||
public void UpdateHealthVisual()
|
||||
{
|
||||
_healthText.Text = $"{Health}/{maxHealth}";
|
||||
}
|
||||
|
||||
public void AttackAnimation(FightAttack attack)
|
||||
{
|
||||
EmitSignalAttacking();
|
||||
var tween = GetTree().CreateTween();
|
||||
tween.TweenProperty(this, "global_position", attack.target.GlobalPosition, 0.15);
|
||||
tween.TweenCallback(Callable.From(() => attack.target?.HitAnimation(attack)));
|
||||
tween.TweenProperty(this, "position", new Vector2(0, 0), 0.7)
|
||||
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
|
||||
|
||||
}
|
||||
|
||||
private void HitAnimation(FightAttack attack)
|
||||
{
|
||||
EmitSignalDamageTaken();
|
||||
var tween = GetTree().CreateTween();
|
||||
tween.TweenProperty(this, "scale", new Vector2(1.4f, 0.6f), 0.15);
|
||||
tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4)
|
||||
.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();
|
||||
var tween = GetTree().CreateTween();
|
||||
tween.TweenProperty(this, "scale", new Vector2(0.6f, 1.4f), 0.15);
|
||||
tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4)
|
||||
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://by88f32fou7lh
|
||||
@@ -0,0 +1,19 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class NextRoomTrigger : Area2D
|
||||
{
|
||||
[Export] private int pathIndex;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
BodyEntered += _OnBodyEnter;
|
||||
}
|
||||
|
||||
private void _OnBodyEnter(Node2D other)
|
||||
{
|
||||
var fss = GetNode<FightSceneSwitcher>("%FightSceneSwitcher");
|
||||
fss.SwitchRoom(pathIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bryibv73x5iwr
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Diagnostics;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Fight;
|
||||
|
||||
public partial class PathSetup : Node
|
||||
{
|
||||
[Export] private int pathId;
|
||||
|
||||
[ExportCategory("Variants")] [Export] private CanvasItem closedVariant;
|
||||
[Export] private CanvasItem nextRoomVariant;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
SetupPathVariant();
|
||||
}
|
||||
|
||||
private void SetupPathVariant()
|
||||
{
|
||||
Debug.Assert(FightWorld.Instance.currentRoom != null);
|
||||
if (FightWorld.Instance.currentRoom.paths.TryGetValue(pathId, out var nextRoom))
|
||||
{
|
||||
ShowOnlyVariant(nextRoomVariant);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowOnlyVariant(closedVariant);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowOnlyVariant(CanvasItem variantToShow)
|
||||
{
|
||||
HideVariant(closedVariant);
|
||||
HideVariant(nextRoomVariant);
|
||||
ShowVariant(variantToShow);
|
||||
}
|
||||
|
||||
private void ShowVariant(CanvasItem variant)
|
||||
{
|
||||
variant.Visible = true;
|
||||
variant.ProcessMode = ProcessModeEnum.Always;
|
||||
}
|
||||
|
||||
private void HideVariant(CanvasItem variant)
|
||||
{
|
||||
variant.Visible = false;
|
||||
variant.ProcessMode = ProcessModeEnum.Disabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dpkx2gbg7b5xh
|
||||
Reference in New Issue
Block a user