Rework game over logic and game initialization

This commit is contained in:
2025-10-27 15:04:01 -07:00
parent 720696aed0
commit 7e6dca1c29
46 changed files with 653 additions and 610 deletions

View File

@@ -2,7 +2,7 @@
namespace Zennysoft.Ma.Adapter;
public interface IAttackComponent
public interface IAttackComponent : IEntityComponent
{
public IAutoProp<int> CurrentAttack { get; }

View File

@@ -2,7 +2,7 @@
namespace Zennysoft.Ma.Adapter;
public interface IDefenseComponent
public interface IDefenseComponent : IEntityComponent
{
public IAutoProp<int> CurrentDefense { get; }

View File

@@ -0,0 +1,6 @@
namespace Zennysoft.Ma.Adapter;
public interface IEntityComponent
{
public void Reset();
}

View File

@@ -2,7 +2,7 @@
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Ma.Adapter;
public interface IEquipmentComponent
public interface IEquipmentComponent : IEntityComponent
{
public IAutoProp<EquipableItem> EquippedWeapon { get; }

View File

@@ -2,7 +2,7 @@
namespace Zennysoft.Ma.Adapter;
public interface IExperiencePointsComponent
public interface IExperiencePointsComponent : IEntityComponent
{
public IAutoProp<int> CurrentExp { get; }

View File

@@ -2,7 +2,7 @@
namespace Zennysoft.Ma.Adapter;
public interface IHealthComponent
public interface IHealthComponent : IEntityComponent
{
public IAutoProp<int> CurrentHP { get; }
@@ -17,7 +17,9 @@ public interface IHealthComponent
public void Damage(int damageAmount);
public void SetHealth(int health);
public void SetCurrentHealth(int health);
public void SetMaximumHealth(int health);
public void RaiseMaximumHP(int raiseAmount);
}
}

View File

@@ -2,7 +2,7 @@
namespace Zennysoft.Ma.Adapter;
public interface ILuckComponent
public interface ILuckComponent : IEntityComponent
{
public IAutoProp<int> Luck { get; }

View File

@@ -2,7 +2,7 @@
namespace Zennysoft.Ma.Adapter;
public interface IVTComponent
public interface IVTComponent : IEntityComponent
{
public IAutoProp<int> CurrentVT { get; }

View File

@@ -8,10 +8,6 @@ public partial class GameState
public readonly record struct LoadGame;
public readonly record struct ContinueGame;
public readonly record struct ReturnToMainMenu;
public readonly record struct LoadNextFloor;
public readonly record struct InventoryButtonPressed;

View File

@@ -16,11 +16,6 @@ public partial class GameState
OnAttach(() => Get<IGameRepo>().Pause());
}
public Transition On(in Input.ReturnToMainMenu input)
{
return To<MainMenu>();
}
public Transition On(in Input.UseTeleport input)
{
Output(new Output.OpenFloorExitScreen());

View File

@@ -8,7 +8,7 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record FloorExitScreen : State, IGet<Input.LoadNextFloor>, IGet<Input.ReturnToMainMenu>
public partial record FloorExitScreen : State, IGet<Input.LoadNextFloor>
{
public FloorExitScreen()
{
@@ -20,11 +20,6 @@ public partial class GameState
Output(new Output.LoadNextFloor());
return To<InGame>();
}
public Transition On(in Input.ReturnToMainMenu input)
{
return To<MainMenu>();
}
}
}
}

View File

@@ -8,18 +8,13 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record GameOver : State, IGet<Input.ContinueGame>, IGet<Input.ReturnToMainMenu>
public partial record GameOver : State, IGet<Input.NewGame>
{
public Transition On(in Input.ContinueGame input)
public Transition On(in Input.NewGame input)
{
Output(new Output.InitializeGame());
return To<InGame>();
}
public Transition On(in Input.ReturnToMainMenu input)
{
return To<MainMenu>();
}
}
}
}

View File

@@ -1,34 +0,0 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace Zennysoft.Ma.Adapter;
public partial class GameState
{
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record MainMenu : State, IGet<Input.NewGame>, IGet<Input.ContinueGame>, IGet<Input.LoadGame>
{
public Transition On(in Input.NewGame input)
{
Output(new Output.InitializeGame());
return To<InGame>();
}
public Transition On(in Input.ContinueGame input)
{
Output(new Output.InitializeGame());
Output(new Output.LoadGameFromFile());
return To<InGame>();
}
public Transition On(in Input.LoadGame input)
{
Output(new Output.InitializeGame());
Output(new Output.LoadGameFromFile());
return To<InGame>();
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Zennysoft.Ma.Adapter
{
public interface IDroppedItem
{
void RescueItem();
public InventoryItem Item { get; }
}
}

View File

@@ -1,9 +1,9 @@
using Zennysoft.Game.Implementation;
namespace Zennysoft.Ma.Adapter;
namespace Zennysoft.Ma.Adapter;
public interface IInventory
{
public bool PickUpItem(InventoryItem item);
public List<InventoryItem> Items { get; }
public bool TryAdd(InventoryItem inventoryItem);
@@ -13,4 +13,6 @@ public interface IInventory
public void Remove(InventoryItem inventoryItem);
public void Sort(EquipableItem currentWeapon, EquipableItem currentArmor, EquipableItem currentAccessory);
public event Action<string> BroadcastMessage;
}

View File

@@ -0,0 +1,6 @@
namespace Zennysoft.Ma.Adapter;
public interface IThrownItem
{
public InventoryItem ItemThatIsThrown { get; set; }
}

View File

@@ -6,6 +6,8 @@ namespace Zennysoft.Ma.Adapter;
public interface IPlayer : IKillable, ICharacterBody3D
{
public void ResetPlayerData();
public void Activate();
public void Deactivate();
@@ -18,6 +20,10 @@ public interface IPlayer : IKillable, ICharacterBody3D
public void TeleportPlayer(Transform3D newTransform);
public void Equip(EquipableItem equipable);
public void Unequip(EquipableItem equipable);
public IInventory Inventory { get; }
public IHealthComponent HealthComponent { get; }
@@ -33,4 +39,7 @@ public interface IPlayer : IKillable, ICharacterBody3D
public ILuckComponent LuckComponent { get; }
public IEquipmentComponent EquipmentComponent { get; }
public event Action PlayerDied;
public delegate InventoryItem RerollItem(InventoryItem item);
}

View File

@@ -10,21 +10,23 @@ public class AttackComponent : IAttackComponent
public IAutoProp<int> MaximumAttack => _maximumAttack;
public IAutoProp<int> BonusAttack => _bonusAttack;
public int TotalAttack => CurrentAttack.Value + BonusAttack.Value;
private readonly AutoProp<int> _currentAttack;
private readonly AutoProp<int> _maximumAttack;
private readonly AutoProp<int> _bonusAttack;
private readonly int _initialValue;
public AttackComponent(int attackValue)
{
_maximumAttack = new AutoProp<int>(attackValue);
_currentAttack = new AutoProp<int>(attackValue);
_bonusAttack = new AutoProp<int>(0);
_initialValue = attackValue;
}
public void Reset()
{
_maximumAttack.OnNext(_initialValue);
_currentAttack.OnNext(_initialValue);
}
public void Restore(int restoreAmount)
@@ -50,14 +52,4 @@ public class AttackComponent : IAttackComponent
_maximumAttack.OnNext(raiseAmount);
Restore(raiseAmount);
}
public void RaiseBonusAttack(int raiseAmount)
{
_bonusAttack.OnNext(_bonusAttack.Value + raiseAmount);
}
public void ResetBonusAttack()
{
_bonusAttack.OnNext(0);
}
}

View File

@@ -14,10 +14,19 @@ public class DefenseComponent : IDefenseComponent
private readonly AutoProp<int> _maximumDefense;
private readonly int _initialValue;
public DefenseComponent(int defenseValue)
{
_maximumDefense = new AutoProp<int>(defenseValue);
_currentDefense = new AutoProp<int>(defenseValue);
_initialValue = defenseValue;
}
public void Reset()
{
_maximumDefense.OnNext(_initialValue);
_currentDefense.OnNext(_initialValue);
}
public void Restore(int restoreAmount)

View File

@@ -36,6 +36,13 @@ public class EquipmentComponent : IEquipmentComponent
_equippedAccessory = new AutoProp<EquipableItem>(new Accessory());
}
public void Reset()
{
_equippedWeapon.OnNext(new Weapon());
_equippedArmor.OnNext(new Armor());
_equippedAccessory.OnNext(new Accessory());
}
public void Equip(EquipableItem equipable)
{
if (equipable is Weapon weapon)

View File

@@ -27,10 +27,19 @@ public class ExperiencePointsComponent : IExperiencePointsComponent
var firstLevelExpRequirement = ExpToNextLevelCalculation(1);
_expToNextLevel = new AutoProp<int>(firstLevelExpRequirement);
_currentExp = new AutoProp<int>(0);
_expGainRate = new AutoProp<double>(1.0);
_expGainRate = new AutoProp<double>(1);
_level = new AutoProp<int>(1);
}
public void Reset()
{
_currentExp.OnNext(0);
_expGainRate.OnNext(1);
_level.OnNext(1);
var firstLevelExpRequirement = ExpToNextLevelCalculation(1);
_expToNextLevel.OnNext(firstLevelExpRequirement);
}
public void Gain(int baseExpGain)
{
var modifiedExpGain = baseExpGain * _expGainRate.Value;

View File

@@ -19,10 +19,19 @@ public class HealthComponent : IHealthComponent
public bool AtFullHealth => CurrentHP.Value == MaximumHP.Value;
private readonly int _initialValue;
public HealthComponent(int initialHP)
{
_maximumHP = new AutoProp<int>(initialHP);
_currentHP = new AutoProp<int>(initialHP);
_initialValue = initialHP;
}
public void Reset()
{
_maximumHP.OnNext(_initialValue);
_currentHP.OnNext(_initialValue);
}
public void Heal(int healAmount)
@@ -42,12 +51,17 @@ public class HealthComponent : IHealthComponent
DamageTaken?.Invoke();
}
public void SetHealth(int health)
public void SetCurrentHealth(int health)
{
var cappedAmount = Math.Min(health, _maximumHP.Value);
_currentHP.OnNext(cappedAmount);
}
public void SetMaximumHealth(int health)
{
_maximumHP.OnNext(health);
}
public void RaiseMaximumHP(int raiseAmount)
{
_maximumHP.OnNext(raiseAmount);

View File

@@ -8,10 +8,17 @@ public class LuckComponent : ILuckComponent
public IAutoProp<int> Luck => _luck;
private AutoProp<int> _luck;
private readonly int _initialValue;
public LuckComponent(int initialLuck)
{
_luck = new AutoProp<int>(initialLuck);
_initialValue = initialLuck;
}
public void Reset()
{
_luck.OnNext(_initialValue);
}
public void IncreaseLuck(int value) => _luck.OnNext(_luck.Value + value);

View File

@@ -14,12 +14,21 @@ public class VTComponent : IVTComponent
private readonly AutoProp<int> _maximumVT;
private readonly int _initialValue;
public bool AtFullVT => CurrentVT.Value == MaximumVT.Value;
public VTComponent(int initialVT)
{
_maximumVT = new AutoProp<int>(initialVT);
_currentVT = new AutoProp<int>(initialVT);
_initialValue = initialVT;
}
public void Reset()
{
_maximumVT.OnNext(_initialValue);
_currentVT.OnNext(_initialValue);
}
public void Restore(int restoreAmount)

View File

@@ -63,5 +63,6 @@ public partial class FollowBehavior : Node3D, IBehavior
{
_thinkTimer.Stop();
_thinkTimer.Timeout -= OnTimeout;
_thinkTimer.Dispose();
}
}

View File

@@ -2707,236 +2707,6 @@ tracks/1/keys = {
"values": [0, 10]
}
[sub_resource type="Animation" id="Animation_6dej3"]
resource_name = "teleport"
length = 0.833341
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"teleport"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.833333),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0, 10]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite3D:scale:x")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.583333, 0.666667, 0.75),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 0,
"values": [1.5, Vector3(0, -8.54, 0), Vector3(0, 14.05, 0), Vector3(0, -13.51, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sprite3D:position")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Sprite3D:scale")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0.25, 0.416667, 0.583333, 0.75, 0.833333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector3(1.5, 1.5, 1.5), Vector3(1.5, -4.765, 1.5), Vector3(7.31, 0.66, 1.5), Vector3(0.397, 2.422, 1.5), Vector3(1.005, 0.005, 1.5)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("Sprite3D:modulate")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0, 0.25, 0.416667, 0.583333, 0.75, 0.833333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(0, 0.291333, 0.92, 0.705882), Color(1, 0, 0, 0.917647), Color(0.3625, 1, 0.15, 0.552941), Color(0.685, 0.3, 1, 0.839216), Color(1, 1, 1, 1)]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("GPUParticles3D:emitting")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0, 0.583333, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [false, false, true]
}
[sub_resource type="Animation" id="Animation_3sdh3"]
resource_name = "teleport in"
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.666667),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0, 10]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D:transparency")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.416667),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [1.0, 0.0]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite3D:scale:x")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.416667, 0.5, 0.583333),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 0,
"values": [1.5, Vector3(0, -8.54, 0), Vector3(0, 14.05, 0), Vector3(0, -13.51, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sprite3D:position")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Sprite3D:scale")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0, 0.0833333, 0.166667, 0.25, 0.416667, 0.583333, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [Vector3(1.005, 0.005, 1.5), Vector3(0.255, -5.205, 1.5), Vector3(0.313, -0.617, 1.5), Vector3(6.281, -0.15, 1.5), Vector3(0.723, 1.868, 1.5), Vector3(3.44, -0.22, 1.5), Vector3(1.5, 1.5, 1.5)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("Sprite3D:modulate")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0.333333, 0.416667, 0.583333, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(0.3625, 1, 0.15, 0.552941), Color(0.685, 0.3, 1, 0.839216), Color(1, 1, 1, 1)]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:modulate")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0, 0.5, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(100, 100, 100, 1), Color(1, 1, 1, 1)]
}
[sub_resource type="Animation" id="Animation_jbhro"]
resource_name = "stop_walk_front"
length = 1.41668
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"idle_front_walk"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1.41667),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0, 17]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite3D:position")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.166667, 0.416667),
"transitions": PackedFloat32Array(1, 5.46418, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, 1.5, 0), Vector3(0, 0, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath(".:CanMove")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0, 0.0833333, 0.416667),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [false, true, false]
}
[sub_resource type="Animation" id="Animation_keq07"]
resource_name = "stop_back_walk"
length = 1.41668
@@ -3120,6 +2890,236 @@ tracks/4/keys = {
"values": [false, true, false]
}
[sub_resource type="Animation" id="Animation_jbhro"]
resource_name = "stop_walk_front"
length = 1.41668
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"idle_front_walk"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1.41667),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0, 17]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite3D:position")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.166667, 0.416667),
"transitions": PackedFloat32Array(1, 5.46418, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, 1.5, 0), Vector3(0, 0, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath(".:CanMove")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0, 0.0833333, 0.416667),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [false, true, false]
}
[sub_resource type="Animation" id="Animation_6dej3"]
resource_name = "teleport"
length = 0.833341
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"teleport"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.833333),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0, 10]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite3D:scale:x")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.583333, 0.666667, 0.75),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 0,
"values": [1.5, Vector3(0, -8.54, 0), Vector3(0, 14.05, 0), Vector3(0, -13.51, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sprite3D:position")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Sprite3D:scale")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0.25, 0.416667, 0.583333, 0.75, 0.833333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector3(1.5, 1.5, 1.5), Vector3(1.5, -4.765, 1.5), Vector3(7.31, 0.66, 1.5), Vector3(0.397, 2.422, 1.5), Vector3(1.005, 0.005, 1.5)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("Sprite3D:modulate")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0, 0.25, 0.416667, 0.583333, 0.75, 0.833333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(0, 0.291333, 0.92, 0.705882), Color(1, 0, 0, 0.917647), Color(0.3625, 1, 0.15, 0.552941), Color(0.685, 0.3, 1, 0.839216), Color(1, 1, 1, 1)]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("GPUParticles3D:emitting")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0, 0.583333, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [false, false, true]
}
[sub_resource type="Animation" id="Animation_3sdh3"]
resource_name = "teleport in"
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.666667),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0, 10]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D:transparency")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.416667),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [1.0, 0.0]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite3D:scale:x")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.416667, 0.5, 0.583333),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 0,
"values": [1.5, Vector3(0, -8.54, 0), Vector3(0, 14.05, 0), Vector3(0, -13.51, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sprite3D:position")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Sprite3D:scale")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0, 0.0833333, 0.166667, 0.25, 0.416667, 0.583333, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [Vector3(1.005, 0.005, 1.5), Vector3(0.255, -5.205, 1.5), Vector3(0.313, -0.617, 1.5), Vector3(6.281, -0.15, 1.5), Vector3(0.723, 1.868, 1.5), Vector3(3.44, -0.22, 1.5), Vector3(1.5, 1.5, 1.5)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("Sprite3D:modulate")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0.333333, 0.416667, 0.583333, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(0.3625, 1, 0.15, 0.552941), Color(0.685, 0.3, 1, 0.839216), Color(1, 1, 1, 1)]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("Sprite3D/SubViewportContainer/SubViewport/AnimatedSprite:modulate")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0, 0.5, 0.666667),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(100, 100, 100, 1), Color(1, 1, 1, 1)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_46p8q"]
_data = {
&"RESET": SubResource("Animation_46p8q"),

View File

@@ -60,38 +60,29 @@ public partial class Game : Node3D, IGame
public RescuedItemDatabase RescuedItems { get; set; } = default!;
public ItemDatabase ItemDatabase { get; private set; }
public QuestData QuestData { get; private set; }
private EffectService _effectService;
private ItemReroller _itemReroller;
private IInstantiator _instantiator;
private Player _player;
private Map _map;
private IPlayer _player;
private IMap _map;
public Game()
{
QuestData = new QuestData();
}
public void Setup()
{
_container = new SimpleInjector.Container();
Module.Bootstrap(_container);
_instantiator = new Instantiator(GetTree());
_player = _instantiator.LoadAndInstantiate<Player>("res://src/player/Player.tscn");
PauseContainer.AddChild(_player);
_map = _instantiator.LoadAndInstantiate<Map>("res://src/map/Map.tscn");
PauseContainer.AddChild(_map);
GameRepo = _container.GetInstance<IGameRepo>();
GameState = _container.GetInstance<IGameState>();
GameState.Set(GameRepo);
GameState.Set(_player);
GameState.Set(_map);
GameState.Set(InGameUI);
QuestData = new QuestData();
RescuedItems = new RescuedItemDatabase();
ItemDatabase = ItemDatabase.Instance;
_itemReroller = new ItemReroller(ItemDatabase);
GameChunk = new SaveChunk<GameData>(
(chunk) =>
@@ -111,15 +102,12 @@ public partial class Game : Node3D, IGame
},
onLoad:
(chunk, data) =>
{
RescuedItems = data.RescuedItems;
QuestData = data.QuestData;
}
{
RescuedItems = data.RescuedItems;
QuestData = data.QuestData;
}
);
}
public void OnResolved()
{
var saveFileManager = _container.GetInstance<IMaSaveFileManager>();
SaveFile = new SaveFile<GameData>(
root: GameChunk,
@@ -139,14 +127,129 @@ public partial class Game : Node3D, IGame
return null;
}
);
}
public void Setup()
{
_instantiator = new Instantiator(GetTree());
_player = _instantiator.LoadAndInstantiate<Player>("res://src/player/Player.tscn");
_map = _instantiator.LoadAndInstantiate<Map>("res://src/map/Map.tscn");
PauseContainer.AddChild((Player)_player);
PauseContainer.AddChild((Map)_map);
}
public async void OnResolved()
{
await InitializeGame();
GameState.Set(GameRepo);
GameState.Set(_player);
GameState.Set(_map);
GameState.Set(InGameUI);
GameRepo.Resume();
InGameUI.Show();
HandleGameLogic();
GameState.Start();
this.Provide();
InGameUI.UseTeleportPrompt.TeleportToNextFloor += UseTeleportPrompt_TeleportToNextFloor;
InGameUI.UseTeleportPrompt.CloseTeleportPrompt += UseTeleportPrompt_CloseTeleportPrompt;
FloorClearMenu.GoToNextFloor += FloorClearMenu_GoToNextFloor;
FloorClearMenu.Exit += FloorClearMenu_Exit;
FloorClearMenu.TransitionCompleted += FloorClearMenu_TransitionCompleted;
GameRepo.RestorativePickedUp += GameEventDepot_RestorativePickedUp;
_player.PlayerDied += GameOver;
DeathMenu.NewGame += OnNewGame;
DeathMenu.QuitGame += OnQuit;
GameRepo.IsPaused.Sync += IsPaused_Sync;
InGameUI.PlayerInfoUI.Activate();
_map.SpawnPointCreated += MovePlayer;
}
public void LoadExistingGame() => SaveFile.Load().ContinueWith((_) => CallDeferred(nameof(FinishedLoadingSaveFile)));
public async Task InitializeGame()
{
_player.ResetPlayerData();
_map.InitializeMapData();
_effectService = new EffectService(this, _player, _map);
_player.Activate();
await _map.LoadFloor();
}
public async Task Save() => await SaveFile.Save();
public void FloorExitReached() => GameState.Input(new GameState.Input.FloorExitEntered());
public async Task UseItem(InventoryItem item)
{
if (item.ItemTag == ItemTag.MysteryItem)
item = _itemReroller.RerollItem(item, _player.Inventory);
switch (item)
{
case ConsumableItem consumableItem:
EnactConsumableItemEffects(consumableItem);
break;
case EffectItem effectItem:
EnactEffectItemEffects(effectItem);
break;
case ThrowableItem throwableItem:
EnactThrowableItemEffects(throwableItem);
break;
}
await ToSignal(GetTree().CreateTimer(0.3f), "timeout");
RemoveItemOrSubtractFromItemCount(item);
}
public void DropItem(InventoryItem item)
{
var droppedScene = GD.Load<PackedScene>("res://src/items/dropped/DroppedItem.tscn");
var dropped = droppedScene.Instantiate<DroppedItem>();
dropped.Item = item;
AddChild(dropped);
dropped.Drop();
}
public void ThrowItem(InventoryItem item)
{
var thrownScene = GD.Load<PackedScene>("res://src/items/thrown/ThrownItem.tscn");
var thrown = thrownScene.Instantiate<ThrownItem>();
thrown.ItemThatIsThrown = item;
AddChild(thrown);
thrown.Position += new Vector3(0, 1.5f, 0);
thrown.Throw(_effectService);
}
public IDungeonFloor CurrentFloor => _map.CurrentFloor;
public void GameOver()
{
_player.Deactivate();
GameState.Input(new GameState.Input.GameOver());
}
public override void _Input(InputEvent @event)
{
if (@event.IsActionPressed(GameInputs.Debug))
GameState.Input(new GameState.Input.DebugButtonPressed());
}
private void HandleGameLogic()
{
GameBinding = GameState.Bind();
GameBinding
.Handle((in GameState.Output.InitializeGame _) =>
{
})
.Handle((in GameState.Output.LoadGameFromFile _) =>
{
LoadExistingGame();
InitializeGame();
})
.Handle((in GameState.Output.OpenPauseScreen _) =>
{
@@ -227,146 +330,17 @@ public partial class Game : Node3D, IGame
GameRepo.Pause();
DeathMenu.FadeIn();
});
GameState.Start();
this.Provide();
InGameUI.UseTeleportPrompt.TeleportToNextFloor += UseTeleportPrompt_TeleportToNextFloor;
InGameUI.UseTeleportPrompt.CloseTeleportPrompt += UseTeleportPrompt_CloseTeleportPrompt;
FloorClearMenu.GoToNextFloor += FloorClearMenu_GoToNextFloor;
FloorClearMenu.SaveAndExit += FloorClearMenu_SaveAndExit;
FloorClearMenu.TransitionCompleted += FloorClearMenu_TransitionCompleted;
GameRepo.RestorativePickedUp += GameEventDepot_RestorativePickedUp;
DeathMenu.NewGame += OnContinueGame;
DeathMenu.QuitGame += OnQuit;
GameRepo.IsPaused.Sync += IsPaused_Sync;
_effectService = new EffectService(this, _player, _map);
}
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
public void OnReady()
{
InitializeGame();
SaveFile.Load();
_map.LoadFloor();
GameRepo.Resume();
InGameUI.Show();
InGameUI.PlayerInfoUI.Activate();
_player.Activate();
}
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
private void FloorClearMenu_SaveAndExit()
private void FloorClearMenu_Exit()
{
_player.Deactivate();
GameState.Input(new GameState.Input.ReturnToMainMenu());
_map.ClearMap();
InGameUI.Hide();
}
private void FloorClearMenu_GoToNextFloor() => GameState.Input(new GameState.Input.LoadNextFloor());
public void LoadExistingGame() => SaveFile.Load().ContinueWith((_) => CallDeferred(nameof(FinishedLoadingSaveFile)));
public void InitializeGame()
{
_map.InitializeMapData();
}
public async Task Save() => await SaveFile.Save();
public void FloorExitReached()
{
GameState.Input(new GameState.Input.FloorExitEntered());
}
public async Task UseItem(InventoryItem item)
{
if (item.ItemTag == ItemTag.MysteryItem)
item = RerollItem(item);
switch (item)
{
case ConsumableItem consumableItem:
EnactConsumableItemEffects(consumableItem);
break;
case EffectItem effectItem:
EnactEffectItemEffects(effectItem);
break;
case ThrowableItem throwableItem:
EnactThrowableItemEffects(throwableItem);
break;
}
await ToSignal(GetTree().CreateTimer(0.3f), "timeout");
RemoveItemOrSubtractFromItemCount(item);
}
public void DropItem(InventoryItem item)
{
var droppedScene = GD.Load<PackedScene>("res://src/items/dropped/DroppedItem.tscn");
var dropped = droppedScene.Instantiate<DroppedItem>();
dropped.Item = item;
AddChild(dropped);
dropped.Drop();
}
public void ThrowItem(InventoryItem item)
{
var thrownScene = GD.Load<PackedScene>("res://src/items/thrown/ThrownItem.tscn");
var thrown = thrownScene.Instantiate<ThrownItem>();
thrown.ItemThatIsThrown = item;
AddChild(thrown);
thrown.Position += new Vector3(0, 1.5f, 0);
thrown.Throw(_effectService);
}
public IDungeonFloor CurrentFloor => _map.CurrentFloor;
public InventoryItem RerollItem(InventoryItem itemToReroll, bool insertIntoInventory = true)
{
var itemDb = new ItemDatabase();
var currentIndex = _player.Inventory.Items.IndexOf(itemToReroll);
if (insertIntoInventory)
_player.Inventory.Remove(itemToReroll);
InventoryItem rolledItem = null;
if (itemToReroll is Weapon weapon)
rolledItem = itemDb.PickItem(weapon);
if (itemToReroll is Armor armor)
rolledItem = itemDb.PickItem(armor);
if (itemToReroll is Accessory accessory)
rolledItem = itemDb.PickItem(accessory);
if (itemToReroll is ThrowableItem throwableItem)
rolledItem = itemDb.PickItem(throwableItem);
if (itemToReroll is EffectItem effectItem)
rolledItem = itemDb.PickItem(effectItem);
if (itemToReroll is ConsumableItem consumableItem)
rolledItem = itemDb.PickItem(consumableItem);
if (insertIntoInventory)
_player.Inventory.TryInsert(rolledItem, currentIndex);
return rolledItem;
}
public void GameOver()
{
GameState.Input(new GameState.Input.GameOver());
}
public override void _Input(InputEvent @event)
{
if (@event.IsActionPressed(GameInputs.Debug))
GameState.Input(new GameState.Input.DebugButtonPressed());
}
private void DropRestorative(Vector3 vector)
{
var restorativeScene = GD.Load<PackedScene>("res://src/items/restorative/Restorative.tscn");
@@ -490,36 +464,28 @@ public partial class Game : Node3D, IGame
GameRepo.RemoveItemFromInventory(item);
}
private void MovePlayer(Transform3D spawnPoint) => _player.TeleportPlayer(spawnPoint);
private void OnNewGame()
{
SaveFile.Load();
GameState.Input(new GameState.Input.NewGame());
GameRepo.Resume();
}
private void OnContinueGame()
{
GameState.Input(new GameState.Input.ContinueGame());
}
private void OnLoadGame()
{
GameState.Input(new GameState.Input.LoadGame());
}
private void OnQuit()
{
}
private void OnQuit() => GetTree().Root.QueueFree();
public void OnExitTree()
{
InGameUI.UseTeleportPrompt.TeleportToNextFloor -= UseTeleportPrompt_TeleportToNextFloor;
InGameUI.UseTeleportPrompt.CloseTeleportPrompt -= UseTeleportPrompt_CloseTeleportPrompt;
FloorClearMenu.GoToNextFloor -= FloorClearMenu_GoToNextFloor;
FloorClearMenu.SaveAndExit -= FloorClearMenu_SaveAndExit;
FloorClearMenu.Exit -= FloorClearMenu_Exit;
FloorClearMenu.TransitionCompleted -= FloorClearMenu_TransitionCompleted;
GameRepo.RestorativePickedUp -= GameEventDepot_RestorativePickedUp;
DeathMenu.NewGame -= OnContinueGame;
DeathMenu.NewGame -= OnNewGame;
DeathMenu.QuitGame -= OnQuit;
GameRepo.IsPaused.Sync -= IsPaused_Sync;

View File

@@ -25,8 +25,6 @@ public interface IGame : IProvide<IGame>, IProvide<IGameRepo>, IProvide<IPlayer>
public void FloorExitReached();
public InventoryItem RerollItem(InventoryItem itemToReroll, bool insertIntoInventory = true);
public void GameOver();
public Task Save();

View File

@@ -1,6 +1,7 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;

View File

@@ -91,8 +91,8 @@ public class EffectService
if (currentRoom is not MonsterRoom)
return;
currentRoom.EnemiesInRoom.ForEach(e => e.HealthComponent.SetHealth(e.HealthComponent.MaximumHP.Value));
_player.HealthComponent.SetHealth(_player.HealthComponent.MaximumHP.Value);
currentRoom.EnemiesInRoom.ForEach(e => e.HealthComponent.SetCurrentHealth(e.HealthComponent.MaximumHP.Value));
_player.HealthComponent.SetCurrentHealth(_player.HealthComponent.MaximumHP.Value);
}
public void AbsorbHPFromAllEnemiesInRoom()
@@ -134,7 +134,7 @@ public class EffectService
var oldHp = _player.HealthComponent.CurrentHP.Value;
var oldVt = _player.VTComponent.CurrentVT.Value;
_player.HealthComponent.SetHealth(oldVt);
_player.HealthComponent.SetCurrentHealth(oldVt);
_player.VTComponent.SetVT(oldHp);
}

View File

@@ -2,6 +2,7 @@
using Chickensoft.Introspection;
using Chickensoft.Serialization;
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Zennysoft.Game.Abstractions;
@@ -17,6 +18,8 @@ public partial class Inventory : Node, IInventory
// TODO: Constants class with export
private const int _maxInventorySize = 20;
public event Action<string> BroadcastMessage;
public Inventory()
{
Items = [];
@@ -25,6 +28,20 @@ public partial class Inventory : Node, IInventory
[Save("inventory_items")]
public List<InventoryItem> Items { get; private set; }
public bool PickUpItem(InventoryItem item)
{
var isAdded = TryAdd(item);
if (isAdded)
{
BroadcastMessage?.Invoke($"{item.ItemName} picked up.");
item.QueueFree();
}
else
BroadcastMessage?.Invoke($"Could not pick up {item.ItemName}.");
return isAdded;
}
public bool TryAdd(InventoryItem inventoryItem)
{
if (Items.Count >= _maxInventorySize)

View File

@@ -1,4 +1,5 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -6,11 +7,16 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
public partial class ItemDatabase : Node
public class ItemDatabase
{
private static readonly Lazy<ItemDatabase> lazy =
new Lazy<ItemDatabase>(() => new ItemDatabase());
public static ItemDatabase Instance { get { return lazy.Value; } }
public ImmutableList<InventoryItem> Items { get; set; }
public InventoryItem PickItem<T>(T itemToExclude = null)
public T PickItem<T>(T itemToExclude = null)
where T : InventoryItem
{
var rng = new RandomNumberGenerator();
@@ -27,10 +33,10 @@ public partial class ItemDatabase : Node
if (selectedItem is ThrowableItem throwableItem)
throwableItem.SetCount(rng.RandiRange(throwableItem.Stats.MinimumCount, throwableItem.Stats.MaximumCount));
return selectedItem;
return (T)selectedItem;
}
public ItemDatabase()
private ItemDatabase()
{
var database = new List<InventoryItem>();
var armorResources = DirAccess.GetFilesAt("res://src/items/armor/resources/");

View File

@@ -0,0 +1,36 @@
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
public class ItemReroller
{
private readonly ItemDatabase _database;
public ItemReroller(ItemDatabase database)
{
_database = database;
}
public T RerollItem<T>(T itemToReroll, IInventory inventory, bool insertIntoInventory = true)
where T : InventoryItem
{
var currentIndex = inventory.Items.IndexOf(itemToReroll);
if (insertIntoInventory)
inventory.Remove(itemToReroll);
var rolledItem = RerollItemInternal((dynamic)itemToReroll);
if (insertIntoInventory)
inventory.TryInsert(rolledItem, currentIndex);
return rolledItem;
}
private Weapon RerollItemInternal(Weapon itemToReroll) => _database.PickItem(itemToReroll);
private Armor RerollItemInternal(Armor itemToReroll) => _database.PickItem(itemToReroll);
private Accessory RerollItemInternal(Accessory itemToReroll) => _database.PickItem(itemToReroll);
private ConsumableItem RerollItemInternal(ConsumableItem itemToReroll) => _database.PickItem(itemToReroll);
private ThrowableItem RerollItemInternal(ThrowableItem itemToReroll) => _database.PickItem(itemToReroll);
private EffectItem RerollItemInternal(EffectItem itemToReroll) => _database.PickItem(itemToReroll);
}

View File

@@ -1,17 +1,10 @@
using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using Zennysoft.Game.Implementation;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
public interface IDroppedItem : IRigidBody3D
{
void RescueItem();
}
[Meta(typeof(IAutoNode))]
public partial class DroppedItem : RigidBody3D, IDroppedItem
{

View File

@@ -7,7 +7,7 @@ using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class ThrownItem : RigidBody3D
public partial class ThrownItem : RigidBody3D, IThrownItem
{
public override void _Notification(int what) => this.Notify(what);
@@ -15,9 +15,10 @@ public partial class ThrownItem : RigidBody3D
[Dependency] public IGame Game => this.DependOn<IGame>();
public InventoryItem ItemThatIsThrown;
public InventoryItem ItemThatIsThrown { get; set; }
private EffectService _effectService;
private ItemReroller _itemReroller;
[Node] public Sprite2D Sprite { get; set; } = default!;
@@ -31,7 +32,7 @@ public partial class ThrownItem : RigidBody3D
Sprite.Texture = ItemThatIsThrown.GetTexture();
AddCollisionExceptionWith((Node)Player);
Collision.SetCollisionLayerValue(3, false);
_itemReroller = new ItemReroller(ItemDatabase.Instance);
}
private void Collision_AreaEntered(Area3D area)
@@ -102,14 +103,14 @@ public partial class ThrownItem : RigidBody3D
private void CalculateEffect(IEnemy enemy)
{
if (ItemThatIsThrown.ItemTag == ItemTag.MysteryItem)
ItemThatIsThrown = Game.RerollItem(ItemThatIsThrown, false);
ItemThatIsThrown = _itemReroller.RerollItem(ItemThatIsThrown, Player.Inventory, false);
if (ItemThatIsThrown is ThrowableItem throwableItem)
{
switch (throwableItem.ThrowableItemTag)
{
case ThrowableItemTag.LowerTargetTo1HP:
enemy.HealthComponent.SetHealth(1);
enemy.HealthComponent.SetCurrentHealth(1);
break;
case ThrowableItemTag.TeleportToRandomLocation:
_effectService.TeleportToRandomRoom(enemy);

View File

@@ -1,6 +1,7 @@
using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Godot;
using System;
using System.Threading.Tasks;
namespace Zennysoft.Game.Ma;
@@ -11,6 +12,8 @@ public interface IMap : INode3D
Task LoadFloor(string sceneName);
void ClearMap();
IDungeonFloor CurrentFloor { get; }
Transform3D GetPlayerSpawnPosition();
@@ -20,4 +23,6 @@ public interface IMap : INode3D
void InitializeMapData();
public AutoProp<int> CurrentFloorNumber { get; }
public event Action<Transform3D> SpawnPointCreated;
}

View File

@@ -1,12 +1,10 @@
using Chickensoft.AutoInject;
using Chickensoft.Collections;
using Chickensoft.Introspection;
using Chickensoft.SaveFileBuilder;
using Godot;
using Godot.Collections;
using System;
using System.Linq;
using System.Threading.Tasks;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
@@ -18,9 +16,6 @@ public partial class Map : Node3D, IMap
[Dependency]
public IGame Game => this.DependOn<IGame>();
[Dependency]
public IPlayer Player => this.DependOn<IPlayer>();
[Node]
public Node MapOrder { get; set; } = default!;
@@ -33,10 +28,11 @@ public partial class Map : Node3D, IMap
private readonly string _floorFilePath = @"res://src/map/dungeon/floors/";
public event Action<Transform3D> SpawnPointCreated;
public void OnResolved()
{
this.Provide();
InitializeMapData();
}
@@ -66,13 +62,20 @@ public partial class Map : Node3D, IMap
public async Task LoadFloor(string sceneName)
{
AnimationPlayer.CallDeferred(AnimationPlayer.MethodName.Play, "fade_out");
ClearCurrentMap();
ClearMap();
var newFloor = await LoadNewFloor(sceneName);
AddChild(newFloor);
InitializeFloor(newFloor);
AnimationPlayer.CallDeferred(AnimationPlayer.MethodName.Play, ("fade_in"));
}
public void ClearMap()
{
AnimationPlayer.CallDeferred(AnimationPlayer.MethodName.Play, "fade_out");
CurrentFloor?.CallDeferred(MethodName.QueueFree, []);
SpawnPointCreated?.Invoke(new Transform3D(Basis.Identity, new Vector3(-999, -999, -999)));
}
private void InitializeFloor(Node newFloor)
{
CurrentFloor = (IDungeonFloor)newFloor;
@@ -92,16 +95,10 @@ public partial class Map : Node3D, IMap
return result;
}
private void ClearCurrentMap()
{
CurrentFloor?.CallDeferred(MethodName.QueueFree, []);
Player.TeleportPlayer(new Transform3D(Basis.Identity, new Vector3(-999, -999, -999)));
}
private void SetupDungeonFloor()
{
CurrentFloor.InitializeDungeon();
var transform = GetPlayerSpawnPosition();
Player.TeleportPlayer(transform);
SpawnPointCreated?.Invoke(transform);
}
}

View File

@@ -56,7 +56,7 @@ public partial class MonsterRoom : DungeonRoom
rng.Randomize();
var numberOfItemsToSpawn = rng.RandiRange(1, itemSpawnPoints.Count);
itemSpawnPoints.Shuffle();
var database = new ItemDatabase();
var database = ItemDatabase.Instance;
foreach (var spawnPoint in itemSpawnPoints.Cast<Marker3D>())
{
if (numberOfItemsToSpawn <= 0)

View File

@@ -1,23 +1,23 @@
using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Chickensoft.SaveFileBuilder;
using Godot;
using SimpleInjector;
using System;
using Zennysoft.Ma.Adapter;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Player : CharacterBody3D, IPlayer
public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
{
#region Dependency Injection
public override void _Notification(int what) => this.Notify(what);
private PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
IPlayer IProvide<IPlayer>.Value() => this;
[Dependency] private IGameRepo _gameRepo => this.DependOn<IGameRepo>(() => new GameRepo());
private PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
#endregion
public IHealthComponent HealthComponent { get; private set; }
@@ -40,16 +40,12 @@ public partial class Player : CharacterBody3D, IPlayer
public IInventory Inventory { get; private set; } = default!;
public event Action PlayerDied;
private PlayerLogic.Settings Settings { get; set; } = default!;
private IPlayerLogic PlayerLogic { get; set; } = default!;
#region Dependencies
[Dependency]
public IGame Game => this.DependOn<IGame>();
#endregion
#region Exports
[ExportGroup("Movement")]
[Export(PropertyHint.Range, "0, 100, 0.1")]
@@ -99,9 +95,17 @@ public partial class Player : CharacterBody3D, IPlayer
private float _knockbackStrength = 0.0f;
private Vector3 _knockbackDirection = Vector3.Zero;
#region Initialization
public Player()
private ItemReroller _itemReroller;
public void Initialize()
{
var container = new SimpleInjector.Container();
container.Register<IPlayerLogic, PlayerLogic>(Lifestyle.Singleton);
PlayerLogic = container.GetInstance<IPlayerLogic>();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
Inventory = new Inventory();
HealthComponent = new HealthComponent(InitialHP);
VTComponent = new VTComponent(InitialVT);
@@ -110,21 +114,9 @@ public partial class Player : CharacterBody3D, IPlayer
ExperiencePointsComponent = new ExperiencePointsComponent();
LuckComponent = new LuckComponent(InitialLuck);
EquipmentComponent = new EquipmentComponent();
}
public void Setup()
{
var container = new SimpleInjector.Container();
container.Register<IPlayerLogic, PlayerLogic>(Lifestyle.Singleton);
_itemReroller = new ItemReroller(ItemDatabase.Instance);
PlayerLogic = container.GetInstance<IPlayerLogic>();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
PlayerLogic.Set(_gameRepo);
}
public void OnResolved()
{
Settings = new PlayerLogic.Settings() { RotationSpeed = RotationSpeed, MoveSpeed = MoveSpeed, Acceleration = Acceleration };
PlayerBinding = PlayerLogic.Bind();
@@ -142,6 +134,23 @@ public partial class Player : CharacterBody3D, IPlayer
this.Provide();
}
public void ResetPlayerData()
{
foreach (var item in Inventory.Items)
Inventory.Remove(item);
HealthComponent.Reset();
VTComponent.Reset();
AttackComponent.Reset();
DefenseComponent.Reset();
ExperiencePointsComponent.Reset();
LuckComponent.Reset();
EquipmentComponent.Reset();
HealthTimer.Timeout += OnHealthTimerTimeout;
}
#region Initialization
public void OnReady()
{
Hitbox.AreaEntered += Hitbox_AreaEntered;
@@ -149,7 +158,6 @@ public partial class Player : CharacterBody3D, IPlayer
SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2;
HealthComponent.HealthReachedZero += Die;
HealthTimer.WaitTime = _healthTimerWaitTime;
HealthTimer.Timeout += OnHealthTimerTimeout;
SetProcessInput(false);
SetPhysicsProcess(false);
}
@@ -210,7 +218,7 @@ public partial class Player : CharacterBody3D, IPlayer
SwordSlashAnimation.Stop();
SetProcessInput(false);
SetPhysicsProcess(false);
Game.GameOver();
PlayerDied?.Invoke();
}
public override void _Input(InputEvent @event)
@@ -233,7 +241,7 @@ public partial class Player : CharacterBody3D, IPlayer
{
if (equipable.ItemTag == ItemTag.MysteryItem)
{
var rerolledItem = Game.RerollItem(equipable) as EquipableItem;
var rerolledItem = _itemReroller.RerollItem(equipable, Inventory);
Equip(rerolledItem);
return;
}
@@ -269,7 +277,6 @@ public partial class Player : CharacterBody3D, IPlayer
if (PlayerIsHittingGeometry())
{
AnimationPlayer.Play("hit_wall");
_gameRepo.OnPlayerAttackedWall();
}
else
{
@@ -291,7 +298,6 @@ public partial class Player : CharacterBody3D, IPlayer
var attackSpeed = ((Weapon)EquipmentComponent.EquippedWeapon.Value).AttackSpeed;
AnimationPlayer.SetSpeedScale((float)attackSpeed);
AnimationPlayer.Play("attack");
_gameRepo.OnPlayerAttack();
}
private void OnExitTree()
@@ -370,40 +376,25 @@ public partial class Player : CharacterBody3D, IPlayer
{
if (area.GetParent() is InventoryItem inventoryItem)
{
var isAdded = Inventory.TryAdd(inventoryItem);
var isAdded = Inventory.PickUpItem(inventoryItem);
if (isAdded)
{
_gameRepo.AnnounceMessageOnMainScreen($"{inventoryItem.ItemName} picked up.");
inventoryItem.QueueFree();
}
else
_gameRepo.AnnounceMessageOnMainScreen($"Could not pick up {inventoryItem.ItemName}.");
}
if (area.GetParent() is DroppedItem droppedItem)
{
var isAdded = Inventory.TryAdd(droppedItem.Item);
var isAdded = Inventory.PickUpItem(droppedItem.Item);
if (isAdded)
{
_gameRepo.AnnounceMessageOnMainScreen($"{droppedItem.Item.ItemName} picked up.");
droppedItem.QueueFree();
}
else
_gameRepo.AnnounceMessageOnMainScreen($"Could not pick up {droppedItem.Item.ItemName}.");
}
if (area.GetParent() is ThrownItem thrownItem)
{
var isAdded = Inventory.TryAdd(thrownItem.ItemThatIsThrown);
var isAdded = Inventory.PickUpItem(thrownItem.ItemThatIsThrown);
if (isAdded)
{
_gameRepo.AnnounceMessageOnMainScreen($"{thrownItem.ItemThatIsThrown.ItemName} picked up.");
thrownItem.QueueFree();
}
else
_gameRepo.AnnounceMessageOnMainScreen($"Could not pick up {thrownItem.ItemThatIsThrown.ItemName}.");
}
if (area.GetParent() is Restorative restorative)
{
_gameRepo.OnRestorativePickedUp(restorative);
//_gameRepo.OnRestorativePickedUp(restorative);
restorative.QueueFree();
}
}

View File

@@ -20,5 +20,6 @@ public partial class QuestTest : Area3D
private void QuestTest_AreaEntered(Area3D area)
{
Game.QuestData.QuestMarker1 = true;
QueueFree();
}
}

View File

@@ -2,7 +2,6 @@ using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;

View File

@@ -9,7 +9,7 @@ public interface IFloorClearMenu : IControl
{
event FloorClearMenu.GoToNextFloorEventHandler GoToNextFloor;
event FloorClearMenu.SaveAndExitEventHandler SaveAndExit;
event FloorClearMenu.ExitEventHandler Exit;
event FloorClearMenu.TransitionCompletedEventHandler TransitionCompleted;
@@ -27,7 +27,7 @@ public partial class FloorClearMenu : Control, IFloorClearMenu
[Node] public Button ContinueButton { get; set; } = default!;
[Node] public Button SaveAndExitButton { get; set; } = default!;
[Node] public Button ExitButton { get; set; } = default!;
public void FadeIn() => AnimationPlayer.Play("fade_in");
@@ -38,27 +38,27 @@ public partial class FloorClearMenu : Control, IFloorClearMenu
[Signal]
public delegate void GoToNextFloorEventHandler();
[Signal]
public delegate void SaveAndExitEventHandler();
public delegate void ExitEventHandler();
public void OnResolved()
{
AnimationPlayer.AnimationFinished += AnimationPlayer_AnimationFinished;
ContinueButton.Pressed += ContinueButton_Pressed;
SaveAndExitButton.Pressed += SaveAndExitButton_Pressed;
ExitButton.Pressed += ExitButton_Pressed;
}
private void SaveAndExitButton_Pressed()
private void ExitButton_Pressed()
{
ContinueButton.Disabled = true;
SaveAndExitButton.Disabled = true;
ExitButton.Disabled = true;
FadeOut();
EmitSignal(SignalName.SaveAndExit);
EmitSignal(SignalName.Exit);
}
private void ContinueButton_Pressed()
{
ContinueButton.Disabled = true;
SaveAndExitButton.Disabled = true;
ExitButton.Disabled = true;
EmitSignal(SignalName.GoToNextFloor);
}
@@ -67,7 +67,7 @@ public partial class FloorClearMenu : Control, IFloorClearMenu
if (animName == "fade_in")
{
ContinueButton.Disabled = false;
SaveAndExitButton.Disabled = false;
ExitButton.Disabled = false;
ContinueButton.CallDeferred(MethodName.GrabFocus);
}
if (animName == "fade_out")

View File

@@ -109,7 +109,7 @@ layout_mode = 2
focus_neighbor_left = NodePath(".")
focus_neighbor_top = NodePath(".")
focus_neighbor_right = NodePath(".")
focus_neighbor_bottom = NodePath("../SaveAndExitButton")
focus_neighbor_bottom = NodePath("../ExitButton")
theme_override_colors/font_color = Color(0.737255, 0.705882, 0.690196, 1)
theme_override_colors/font_focus_color = Color(1, 0.94902, 0, 1)
theme_override_fonts/font = ExtResource("2_xk0dh")
@@ -120,7 +120,7 @@ button_mask = 0
text = "Continue"
flat = true
[node name="SaveAndExitButton" type="Button" parent="CenterContainer/VBoxContainer"]
[node name="ExitButton" type="Button" parent="CenterContainer/VBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(200, 50)
layout_mode = 2
@@ -135,5 +135,5 @@ theme_override_font_sizes/font_size = 36
theme_override_styles/focus = SubResource("StyleBoxEmpty_cyd1c")
theme_override_styles/normal = SubResource("StyleBoxEmpty_4bdva")
button_mask = 0
text = "Save and Exit"
text = "Exit Tower"
flat = true

View File

@@ -28,8 +28,6 @@ public partial class InventoryMenu : Control, IInventoryMenu
[Dependency] public IPlayer Player => this.DependOn<IPlayer>();
[Dependency] public IMap _map => this.DependOn<IMap>();
private InventoryPageNumber _currentPageNumber = InventoryPageNumber.FirstPage;
private string ITEM_SLOT_SCENE = "res://src/ui/inventory_menu/ItemSlot.tscn";
@@ -62,11 +60,24 @@ public partial class InventoryMenu : Control, IInventoryMenu
[Node] public AnimationPlayer AnimationPlayer { get; set; } = default!;
#endregion
public void OnReady()
public InventoryMenu()
{
SetProcessInput(false);
SetProcess(false);
}
public void OnResolved()
{
UseButton.Pressed += UseButtonPressed;
ThrowButton.Pressed += ThrowButtonPressed;
DropButton.Pressed += DropButtonPressed;
Player.AttackComponent.CurrentAttack.Sync += AttackSync;
Player.AttackComponent.MaximumAttack.Sync += AttackSync;
Player.EquipmentComponent.EquippedWeapon.Sync += BonusSync;
Player.EquipmentComponent.EquippedArmor.Sync += BonusSync;
Player.EquipmentComponent.EquippedAccessory.Sync += BonusSync;
Player.DefenseComponent.CurrentDefense.Sync += DefenseSync;
Player.DefenseComponent.MaximumDefense.Sync += DefenseSync;
}
public void OnExitTree()
@@ -80,18 +91,6 @@ public partial class InventoryMenu : Control, IInventoryMenu
Player.DefenseComponent.MaximumDefense.Sync -= DefenseSync;
}
public void OnResolved()
{
SetProcessInput(false);
Player.AttackComponent.CurrentAttack.Sync += AttackSync;
Player.AttackComponent.MaximumAttack.Sync += AttackSync;
Player.EquipmentComponent.EquippedWeapon.Sync += BonusSync;
Player.EquipmentComponent.EquippedArmor.Sync += BonusSync;
Player.EquipmentComponent.EquippedAccessory.Sync += BonusSync;
Player.DefenseComponent.CurrentDefense.Sync += DefenseSync;
Player.DefenseComponent.MaximumDefense.Sync += DefenseSync;
}
public async Task DisplayMessage(string message)
{
SetProcessInput(false);
@@ -125,8 +124,6 @@ public partial class InventoryMenu : Control, IInventoryMenu
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
public override void _Input(InputEvent @event)
{
var inventory = Player.Inventory;
if (@event.IsActionPressed(GameInputs.UiCancel))
{
if (UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())
@@ -145,8 +142,12 @@ public partial class InventoryMenu : Control, IInventoryMenu
if (ItemSlots.Length == 0 || UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())
return;
if (@event.IsActionPressed(GameInputs.UiRight) && _currentPageNumber == InventoryPageNumber.FirstPage && inventory.Items.Count > _itemsPerPage)
ChangeInventoryPage(InventoryPageNumber.SecondPage);
if (@event.IsActionPressed(GameInputs.UiRight) && _currentPageNumber == InventoryPageNumber.FirstPage)
{
var inventory = Player.Inventory;
if (inventory.Items.Count > _itemsPerPage)
ChangeInventoryPage(InventoryPageNumber.SecondPage);
}
if (@event.IsActionPressed(GameInputs.UiLeft) && _currentPageNumber == InventoryPageNumber.SecondPage)
ChangeInventoryPage(InventoryPageNumber.FirstPage);
@@ -186,6 +187,7 @@ public partial class InventoryMenu : Control, IInventoryMenu
if (@event.IsActionPressed(GameInputs.InventorySort))
{
var inventory = Player.Inventory;
inventory.Sort(Player.EquipmentComponent.EquippedWeapon.Value, Player.EquipmentComponent.EquippedArmor.Value, Player.EquipmentComponent.EquippedAccessory.Value);
if (_currentIndex > inventory.Items.Count - 1)
_currentIndex = inventory.Items.Count - 1;
@@ -357,7 +359,7 @@ public partial class InventoryMenu : Control, IInventoryMenu
else
{
ItemEffectLabel.Text = $"{equippableItem.GetType().Name} equipped.";
Player.EquipmentComponent.Equip(equippableItem);
Player.Equip(equippableItem);
itemSlot.SetEquippedSelectedItemStyle();
}

View File

@@ -38,7 +38,7 @@ public partial class PauseDebugMenu : Control, IDebugMenu
{
LoadNextFloorButton.Pressed += LoadNextFloorButton_Pressed;
_itemDatabase = new ItemDatabase();
_itemDatabase = ItemDatabase.Instance;
_spawnableItems = _itemDatabase.Items;
_spawnableEnemies =

View File

@@ -18,6 +18,7 @@ public partial class SceneLoader : Node
ResourceLoader.LoadThreadedRequest(sceneToLoad);
_sceneToLoad = sceneToLoad;
_loading = true;
SetProcess(true);
}
private void LoadScene()