From 7b6ca68e6511462bf66421ac227825905d24a957 Mon Sep 17 00:00:00 2001 From: Zenny Date: Mon, 2 Mar 2026 22:28:23 -0800 Subject: [PATCH] Rust --- .../Components/IStatusEffectComponent.cs | 13 +++++ .../Entity/IEnemy.cs | 2 + .../Game/GameRepo.cs | 23 --------- .../Player/IPlayer.cs | 2 + .../src/Components/StatusEffectComponent.cs | 20 ++++++++ .../Components/StatusEffectComponent.cs.uid | 1 + Zennysoft.Game.Ma/src/enemy/Enemy.cs | 45 +++++++++++++++++ Zennysoft.Game.Ma/src/game/Game.cs | 49 ++++++++++++++++++- .../items/consumable/ConsumableItemStats.cs | 3 ++ .../consumable/resources/AncientCapsule.tres | 1 + .../src/items/weapons/Weapon.tscn | 2 +- Zennysoft.Game.Ma/src/menu/DebugMenu.tscn | 5 ++ Zennysoft.Game.Ma/src/player/DummyPlayer.cs | 1 + Zennysoft.Game.Ma/src/player/Player.cs | 18 +++++++ .../src/ui/pause_menu/PauseDebugMenu.cs | 5 ++ 15 files changed, 164 insertions(+), 26 deletions(-) create mode 100644 Zennysoft.Game.Ma.Implementation/Components/IStatusEffectComponent.cs create mode 100644 Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs create mode 100644 Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs.uid diff --git a/Zennysoft.Game.Ma.Implementation/Components/IStatusEffectComponent.cs b/Zennysoft.Game.Ma.Implementation/Components/IStatusEffectComponent.cs new file mode 100644 index 000000000..473b6a29a --- /dev/null +++ b/Zennysoft.Game.Ma.Implementation/Components/IStatusEffectComponent.cs @@ -0,0 +1,13 @@ + +using Chickensoft.Collections; +using Godot; +using Zennysoft.Ma.Adapter; + +public interface IStatusEffectComponent : IEntityComponent +{ + [Export] public double RustDuration { get; set; } + + public AutoProp Rust { get; } + + public bool ImmuneToRust { get; set; } +} diff --git a/Zennysoft.Game.Ma.Implementation/Entity/IEnemy.cs b/Zennysoft.Game.Ma.Implementation/Entity/IEnemy.cs index 3d81f4c89..9384ac5b4 100644 --- a/Zennysoft.Game.Ma.Implementation/Entity/IEnemy.cs +++ b/Zennysoft.Game.Ma.Implementation/Entity/IEnemy.cs @@ -31,6 +31,8 @@ namespace Zennysoft.Ma.Adapter.Entity public IDefenseComponent DefenseComponent { get; } + public IStatusEffectComponent StatusEffectComponent { get; } + public ElementalResistanceSet ElementalResistanceSet { get; } public int InitialHP { get; } diff --git a/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs b/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs index 3c74660a8..4aff6dc5b 100644 --- a/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs +++ b/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs @@ -18,12 +18,6 @@ public interface IGameRepo : IDisposable event Action? RemoveItemFromInventoryEvent; - event Action? PlayerAttack; - - event Action? PlayerAttackedWall; - - event Action? PlayerAttackedEnemy; - event Action? EquippedItem; event Action? UnequippedItem; @@ -42,10 +36,6 @@ public interface IGameRepo : IDisposable public void RemoveItemFromInventory(IBaseInventoryItem item); - public void OnPlayerAttack(); - - public void OnPlayerAttackedWall(); - public void CloseInventory(); public void GameEnded(); @@ -64,9 +54,6 @@ public class GameRepo : IGameRepo public event Action? AnnounceMessageOnMainScreenEvent; public event Action? AnnounceMessageInInventoryEvent; public event Action? RemoveItemFromInventoryEvent; - public event Action? PlayerAttack; - public event Action? PlayerAttackedWall; - public event Action? PlayerAttackedEnemy; public event Action? EquippedItem; public event Action? UnequippedItem; public event Action? EnemyDied; @@ -107,16 +94,6 @@ public class GameRepo : IGameRepo RemoveItemFromInventoryEvent?.Invoke(item); } - public void OnPlayerAttack() - { - PlayerAttack?.Invoke(); - } - - public void OnPlayerAttackedWall() - { - PlayerAttackedWall?.Invoke(); - } - public void CloseInventory() { CloseInventoryEvent?.Invoke(); diff --git a/Zennysoft.Game.Ma.Implementation/Player/IPlayer.cs b/Zennysoft.Game.Ma.Implementation/Player/IPlayer.cs index 65edb78ba..2c25eb13b 100644 --- a/Zennysoft.Game.Ma.Implementation/Player/IPlayer.cs +++ b/Zennysoft.Game.Ma.Implementation/Player/IPlayer.cs @@ -46,6 +46,8 @@ public interface IPlayer : IKillable, ICharacterBody3D public IEquipmentComponent EquipmentComponent { get; } + public IStatusEffectComponent StatusEffectComponent { get; } + public void SetHealthTimerStatus(bool isActive); public void ModifyHealthTimerSpeed(float newModifier); diff --git a/Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs b/Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs new file mode 100644 index 000000000..7d791ad62 --- /dev/null +++ b/Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs @@ -0,0 +1,20 @@ +using Chickensoft.Collections; + +public class StatusEffectComponent : IStatusEffectComponent +{ + public StatusEffectComponent(double rustDuration) + { + RustDuration = rustDuration; + } + + public double RustDuration { get; set; } + + public AutoProp Rust { get; } = new AutoProp(false); + + public bool ImmuneToRust { get; set; } = false; + + public void Reset() + { + Rust.OnNext(false); + } +} diff --git a/Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs.uid b/Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs.uid new file mode 100644 index 000000000..2c21f2587 --- /dev/null +++ b/Zennysoft.Game.Ma/src/Components/StatusEffectComponent.cs.uid @@ -0,0 +1 @@ +uid://chhmivq4bntxf diff --git a/Zennysoft.Game.Ma/src/enemy/Enemy.cs b/Zennysoft.Game.Ma/src/enemy/Enemy.cs index bca8a0f82..ff596fe2b 100644 --- a/Zennysoft.Game.Ma/src/enemy/Enemy.cs +++ b/Zennysoft.Game.Ma/src/enemy/Enemy.cs @@ -34,6 +34,8 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide @@ -160,6 +179,8 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide InventoryEventNotification; @@ -191,6 +194,19 @@ public partial class Game : Node3D, IGame _doubleExpTimer.Timeout += EndDoubleExpTimer; AddChild(_doubleExpTimer); + _player.StatusEffectComponent.Rust.Changed += RustStatusChanged; + + _rustTimer = new Timer(); + _rustTimer.WaitTime = 2f; + _rustTimer.Timeout += RustTimeout; + + _rustDurationTimer = new Timer(); + _rustDurationTimer.WaitTime = _player.StatusEffectComponent.RustDuration; + _rustDurationTimer.Timeout += RustWoreOff; + + AddChild(_rustTimer); + AddChild(_rustDurationTimer); + GameRepo.IsPaused.Sync += IsPaused_Sync; InGameUI.PlayerInfoUI.Activate(); InGameUI.Show(); @@ -414,16 +430,16 @@ public partial class Game : Node3D, IGame { var restorativeScene = GD.Load("res://src/items/restorative/Restorative.tscn"); var restorative = restorativeScene.Instantiate(); - restorative.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z); AddChild(restorative); + restorative.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z); } private void DropItem(Vector3 vector) { var randomItem = ItemDatabase.Instance.PickItem() as Node3D; var duplicated = randomItem.Duplicate((int)DuplicateFlags.UseInstantiation) as Node3D; - duplicated.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z); AddChild(duplicated); + duplicated.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z); } private void UseTeleportPrompt_CloseTeleportPrompt() @@ -576,6 +592,12 @@ public partial class Game : Node3D, IGame SfxDatabase.Instance.Play(SoundEffect.HealVT); InventoryEventNotification.Invoke($"Restored {consumableItem.HealVTAmount} VT."); } + + if (consumableItem.Stats.HealsStatusAilments) + { + _player.StatusEffectComponent.Reset(); + InventoryEventNotification.Invoke($"All status afflictments have faded."); + } } private void EnactEffectItemEffects(EffectItem effectItem) @@ -809,6 +831,29 @@ public partial class Game : Node3D, IGame _player.Activate(); } + private void RustStatusChanged(bool rustStatus) + { + if (rustStatus) + { + _rustTimer.Start(); + _rustDurationTimer.Start(); + GameRepo.AnnounceMessageOnMainScreen("Afflicted with Rust."); + } + else + { + _rustTimer.Stop(); + GameRepo.AnnounceMessageOnMainScreen("Rust affliction has faded."); + } + } + + private void RustTimeout() + { + _player.HealthComponent.Damage(3); + } + + private void RustWoreOff() => _player.StatusEffectComponent.Rust.OnNext(false); + + public void NotifyInventory(string message) => InventoryEventNotification?.Invoke(message); private void OnQuit() => GameExitRequested?.Invoke(); diff --git a/Zennysoft.Game.Ma/src/items/consumable/ConsumableItemStats.cs b/Zennysoft.Game.Ma/src/items/consumable/ConsumableItemStats.cs index 7578812ee..9eec26735 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/ConsumableItemStats.cs +++ b/Zennysoft.Game.Ma/src/items/consumable/ConsumableItemStats.cs @@ -21,4 +21,7 @@ public partial class ConsumableItemStats : InventoryItemStats [Export] [Save("consumable_item_raise_vt")] public int PermanentRaiseVTAmount { get; set; } = 0; + + [Export] + public bool HealsStatusAilments { get; set; } = false; } diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/AncientCapsule.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/AncientCapsule.tres index b5b2ada4e..1ac828f24 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/AncientCapsule.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/AncientCapsule.tres @@ -9,6 +9,7 @@ HealHPAmount = 0 HealVTAmount = 0 PermanentRaiseHPAmount = 0 PermanentRaiseVTAmount = 0 +HealsStatusAilments = true Name = "Ancient Capsule" StatDescription = "Heals all status ailments." FlavorText = "" diff --git a/Zennysoft.Game.Ma/src/items/weapons/Weapon.tscn b/Zennysoft.Game.Ma/src/items/weapons/Weapon.tscn index 8f950243e..2e87c806d 100644 --- a/Zennysoft.Game.Ma/src/items/weapons/Weapon.tscn +++ b/Zennysoft.Game.Ma/src/items/weapons/Weapon.tscn @@ -8,7 +8,7 @@ height = 0.725098 [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_wll7p"] radius = 0.470016 -[node name="Ammo" type="RigidBody3D"] +[node name="Weapon" type="RigidBody3D"] collision_layer = 0 axis_lock_linear_x = true axis_lock_linear_z = true diff --git a/Zennysoft.Game.Ma/src/menu/DebugMenu.tscn b/Zennysoft.Game.Ma/src/menu/DebugMenu.tscn index 29442b5be..ec7b0bbd4 100644 --- a/Zennysoft.Game.Ma/src/menu/DebugMenu.tscn +++ b/Zennysoft.Game.Ma/src/menu/DebugMenu.tscn @@ -125,6 +125,11 @@ unique_name_in_owner = true layout_mode = 2 text = "Die" +[node name="RustButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer/VFlowContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Rust" + [node name="DebugInfoCheckbox" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer/VFlowContainer"] unique_name_in_owner = true layout_mode = 2 diff --git a/Zennysoft.Game.Ma/src/player/DummyPlayer.cs b/Zennysoft.Game.Ma/src/player/DummyPlayer.cs index 71e8aa328..997762bd2 100644 --- a/Zennysoft.Game.Ma/src/player/DummyPlayer.cs +++ b/Zennysoft.Game.Ma/src/player/DummyPlayer.cs @@ -21,6 +21,7 @@ public partial class DummyPlayer : CharacterBody3D, IPlayer public int HealthTimerHPRate { get; set; } public float HealthTimerSpeedModifier { get; } public bool AutoIdentifyItems { get; set; } + public IStatusEffectComponent StatusEffectComponent { get; } public event Action PlayerDied; diff --git a/Zennysoft.Game.Ma/src/player/Player.cs b/Zennysoft.Game.Ma/src/player/Player.cs index af861c6b7..310e0148f 100644 --- a/Zennysoft.Game.Ma/src/player/Player.cs +++ b/Zennysoft.Game.Ma/src/player/Player.cs @@ -37,6 +37,8 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide public IEquipmentComponent EquipmentComponent { get; private set; } + public IStatusEffectComponent StatusEffectComponent { get; private set; } + public Vector3 CurrentPosition => GlobalPosition; public Basis CurrentBasis => Transform.Basis; @@ -70,6 +72,8 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide [Export(PropertyHint.Range, "1, 100, 1")] public int InitialLuck { get; set; } = 8; + [Export(PropertyHint.Range, "1, 60, 1")] public double RustDuration { get; set; } = 30; + [Export] private bool HealthTimerIsActive = false; #endregion @@ -149,6 +153,7 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide ExperiencePointsComponent = new ExperiencePointsComponent(); LuckComponent = new LuckComponent(InitialLuck); EquipmentComponent = new EquipmentComponent(); + StatusEffectComponent = new StatusEffectComponent(RustDuration); _itemReroller = new ItemReroller(ItemDatabase.Instance); _playerEffectService = new PlayerEffectService(this); @@ -872,6 +877,19 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide _playerEffectService.TakeSelfDamage(weapon.Stats.SelfDamage); if (weapon.WeaponTag == WeaponTag.Instakill) _playerEffectService.Instakill(enemy); + if (weapon.WeaponTag == WeaponTag.RustChanceSelfAndEnemy) + { + var rustChance = 0.15f; + var rng = new RandomNumberGenerator(); + rng.Randomize(); + if (rng.Randf() <= rustChance) + { + if (rng.Randf() >= 0.5f && ((Accessory)EquipmentComponent.EquippedAccessory.Value).AccessoryTag != AccessoryTag.StatusEffectImmunity) + StatusEffectComponent.Rust.OnNext(true); + else + enemy.StatusEffectComponent.Rust.OnNext(true); + } + } } private async void CollisionDetector_AreaEntered(Area3D area) diff --git a/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs b/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs index fd5ad0e5a..78a6bdb43 100644 --- a/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs +++ b/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs @@ -26,6 +26,8 @@ public partial class PauseDebugMenu : Control, IDebugMenu [Node] public Button LoadNextFloorButton { get; set; } = default!; + [Node] public Button RustButton { get; set; } = default!; + [Node] public Button DieButton { get; set; } = default!; [Node] public CheckBox DebugInfoCheckbox { get; set; } = default!; @@ -42,6 +44,7 @@ public partial class PauseDebugMenu : Control, IDebugMenu { LoadNextFloorButton.Pressed += LoadNextFloorButton_Pressed; DieButton.Pressed += DieButton_Pressed; + RustButton.Pressed += RustButton_Pressed; _itemDatabase = ItemDatabase.Instance; _spawnableItems = _itemDatabase.Items; @@ -94,6 +97,8 @@ public partial class PauseDebugMenu : Control, IDebugMenu private void DieButton_Pressed() => _player.Die(); + private void RustButton_Pressed() => _player.StatusEffectComponent.Rust.OnNext(true); + private void FloorSelectDropDown_ItemSelected(long index) { var sceneName = FloorSelectDropDown.GetItemText((int)index);