This commit is contained in:
2026-03-02 22:28:23 -08:00
parent 701e7b0858
commit 7b6ca68e65
15 changed files with 164 additions and 26 deletions

View File

@@ -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<bool> Rust { get; }
public bool ImmuneToRust { get; set; }
}

View File

@@ -31,6 +31,8 @@ namespace Zennysoft.Ma.Adapter.Entity
public IDefenseComponent DefenseComponent { get; } public IDefenseComponent DefenseComponent { get; }
public IStatusEffectComponent StatusEffectComponent { get; }
public ElementalResistanceSet ElementalResistanceSet { get; } public ElementalResistanceSet ElementalResistanceSet { get; }
public int InitialHP { get; } public int InitialHP { get; }

View File

@@ -18,12 +18,6 @@ public interface IGameRepo : IDisposable
event Action<IBaseInventoryItem>? RemoveItemFromInventoryEvent; event Action<IBaseInventoryItem>? RemoveItemFromInventoryEvent;
event Action? PlayerAttack;
event Action? PlayerAttackedWall;
event Action? PlayerAttackedEnemy;
event Action<IEquipableItem>? EquippedItem; event Action<IEquipableItem>? EquippedItem;
event Action<IEquipableItem>? UnequippedItem; event Action<IEquipableItem>? UnequippedItem;
@@ -42,10 +36,6 @@ public interface IGameRepo : IDisposable
public void RemoveItemFromInventory(IBaseInventoryItem item); public void RemoveItemFromInventory(IBaseInventoryItem item);
public void OnPlayerAttack();
public void OnPlayerAttackedWall();
public void CloseInventory(); public void CloseInventory();
public void GameEnded(); public void GameEnded();
@@ -64,9 +54,6 @@ public class GameRepo : IGameRepo
public event Action<string>? AnnounceMessageOnMainScreenEvent; public event Action<string>? AnnounceMessageOnMainScreenEvent;
public event Action<string>? AnnounceMessageInInventoryEvent; public event Action<string>? AnnounceMessageInInventoryEvent;
public event Action<IBaseInventoryItem>? RemoveItemFromInventoryEvent; public event Action<IBaseInventoryItem>? RemoveItemFromInventoryEvent;
public event Action? PlayerAttack;
public event Action? PlayerAttackedWall;
public event Action? PlayerAttackedEnemy;
public event Action<IEquipableItem>? EquippedItem; public event Action<IEquipableItem>? EquippedItem;
public event Action<IEquipableItem>? UnequippedItem; public event Action<IEquipableItem>? UnequippedItem;
public event Action<IEnemy>? EnemyDied; public event Action<IEnemy>? EnemyDied;
@@ -107,16 +94,6 @@ public class GameRepo : IGameRepo
RemoveItemFromInventoryEvent?.Invoke(item); RemoveItemFromInventoryEvent?.Invoke(item);
} }
public void OnPlayerAttack()
{
PlayerAttack?.Invoke();
}
public void OnPlayerAttackedWall()
{
PlayerAttackedWall?.Invoke();
}
public void CloseInventory() public void CloseInventory()
{ {
CloseInventoryEvent?.Invoke(); CloseInventoryEvent?.Invoke();

View File

@@ -46,6 +46,8 @@ public interface IPlayer : IKillable, ICharacterBody3D
public IEquipmentComponent EquipmentComponent { get; } public IEquipmentComponent EquipmentComponent { get; }
public IStatusEffectComponent StatusEffectComponent { get; }
public void SetHealthTimerStatus(bool isActive); public void SetHealthTimerStatus(bool isActive);
public void ModifyHealthTimerSpeed(float newModifier); public void ModifyHealthTimerSpeed(float newModifier);

View File

@@ -0,0 +1,20 @@
using Chickensoft.Collections;
public class StatusEffectComponent : IStatusEffectComponent
{
public StatusEffectComponent(double rustDuration)
{
RustDuration = rustDuration;
}
public double RustDuration { get; set; }
public AutoProp<bool> Rust { get; } = new AutoProp<bool>(false);
public bool ImmuneToRust { get; set; } = false;
public void Reset()
{
Rust.OnNext(false);
}
}

View File

@@ -0,0 +1 @@
uid://chhmivq4bntxf

View File

@@ -34,6 +34,8 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
public IDefenseComponent DefenseComponent { get; private set; } public IDefenseComponent DefenseComponent { get; private set; }
public IStatusEffectComponent StatusEffectComponent { get; private set; }
public virtual IEnemyModelView EnemyModelView { get; set; } = default!; public virtual IEnemyModelView EnemyModelView { get; set; } = default!;
public Vector3 TargetPosition { get; private set; } public Vector3 TargetPosition { get; private set; }
@@ -69,6 +71,9 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
[Node] private AudioStreamPlayer3D _dieSFX { get; set; } = default!; [Node] private AudioStreamPlayer3D _dieSFX { get; set; } = default!;
[Node] private AudioStreamPlayer3D _aggroSFX { get; set; } = default!; [Node] private AudioStreamPlayer3D _aggroSFX { get; set; } = default!;
private Timer _rustTimer;
private Timer _rustDuration;
protected bool _activated = false; protected bool _activated = false;
private Vector3 _previousPosition = Vector3.Zero; private Vector3 _previousPosition = Vector3.Zero;
@@ -86,8 +91,22 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
HealthComponent.HealthReachedZero += Die; HealthComponent.HealthReachedZero += Die;
HealthComponent.DamageTaken += TakeHit; HealthComponent.DamageTaken += TakeHit;
_rustTimer = new Timer();
_rustDuration = new Timer();
_rustTimer.WaitTime = 3;
_rustDuration.WaitTime = 30;
_rustTimer.Timeout += _rustTimer_Timeout;
_rustDuration.Timeout += _rustDuration_Timeout;
AddChild(_rustTimer);
AddChild(_rustDuration);
AttackComponent = new AttackComponent(InitialAttack); AttackComponent = new AttackComponent(InitialAttack);
DefenseComponent = new DefenseComponent(InitialDefense); DefenseComponent = new DefenseComponent(InitialDefense);
StatusEffectComponent = new StatusEffectComponent(30);
StatusEffectComponent.Rust.Changed += OnRusted;
EnemyBinding EnemyBinding
.Handle((in EnemyLogic.Output.Activate _) => .Handle((in EnemyLogic.Output.Activate _) =>
@@ -160,6 +179,8 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
public virtual void Die() public virtual void Die()
{ {
SetPhysicsProcess(false); SetPhysicsProcess(false);
_rustDuration.Stop();
_rustTimer.Stop();
_enemyLogic.Input(new EnemyLogic.Input.Defeated()); _enemyLogic.Input(new EnemyLogic.Input.Defeated());
_player.ExperiencePointsComponent.Gain(ExpGiven); _player.ExperiencePointsComponent.Gain(ExpGiven);
EnemyModelView.PlayDeathAnimation(); EnemyModelView.PlayDeathAnimation();
@@ -244,4 +265,28 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
{ {
_player.TakeDamage(new AttackData(AttackComponent.CurrentAttack.Value, ElementType.None)); _player.TakeDamage(new AttackData(AttackComponent.CurrentAttack.Value, ElementType.None));
} }
private void OnRusted(bool rustStatus)
{
if (rustStatus)
{
_rustTimer.Start();
_rustDuration.Start();
}
else
{
_rustTimer.Stop();
}
}
private void _rustTimer_Timeout()
{
HealthComponent.Damage(3);
TakeHit();
}
private void _rustDuration_Timeout()
{
StatusEffectComponent.Rust.OnNext(false);
}
} }

View File

@@ -80,6 +80,9 @@ public partial class Game : Node3D, IGame
private Timer _doubleExpTimer; private Timer _doubleExpTimer;
private Timer _rustTimer;
private Timer _rustDurationTimer;
[Signal] private delegate void OnLoadLevelRequestEventHandler(); [Signal] private delegate void OnLoadLevelRequestEventHandler();
public event Action<string> InventoryEventNotification; public event Action<string> InventoryEventNotification;
@@ -191,6 +194,19 @@ public partial class Game : Node3D, IGame
_doubleExpTimer.Timeout += EndDoubleExpTimer; _doubleExpTimer.Timeout += EndDoubleExpTimer;
AddChild(_doubleExpTimer); 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; GameRepo.IsPaused.Sync += IsPaused_Sync;
InGameUI.PlayerInfoUI.Activate(); InGameUI.PlayerInfoUI.Activate();
InGameUI.Show(); InGameUI.Show();
@@ -414,16 +430,16 @@ public partial class Game : Node3D, IGame
{ {
var restorativeScene = GD.Load<PackedScene>("res://src/items/restorative/Restorative.tscn"); var restorativeScene = GD.Load<PackedScene>("res://src/items/restorative/Restorative.tscn");
var restorative = restorativeScene.Instantiate<Restorative>(); var restorative = restorativeScene.Instantiate<Restorative>();
restorative.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z);
AddChild(restorative); AddChild(restorative);
restorative.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z);
} }
private void DropItem(Vector3 vector) private void DropItem(Vector3 vector)
{ {
var randomItem = ItemDatabase.Instance.PickItem<IBaseInventoryItem>() as Node3D; var randomItem = ItemDatabase.Instance.PickItem<IBaseInventoryItem>() as Node3D;
var duplicated = randomItem.Duplicate((int)DuplicateFlags.UseInstantiation) 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); AddChild(duplicated);
duplicated.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z);
} }
private void UseTeleportPrompt_CloseTeleportPrompt() private void UseTeleportPrompt_CloseTeleportPrompt()
@@ -576,6 +592,12 @@ public partial class Game : Node3D, IGame
SfxDatabase.Instance.Play(SoundEffect.HealVT); SfxDatabase.Instance.Play(SoundEffect.HealVT);
InventoryEventNotification.Invoke($"Restored {consumableItem.HealVTAmount} VT."); 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) private void EnactEffectItemEffects(EffectItem effectItem)
@@ -809,6 +831,29 @@ public partial class Game : Node3D, IGame
_player.Activate(); _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); public void NotifyInventory(string message) => InventoryEventNotification?.Invoke(message);
private void OnQuit() => GameExitRequested?.Invoke(); private void OnQuit() => GameExitRequested?.Invoke();

View File

@@ -21,4 +21,7 @@ public partial class ConsumableItemStats : InventoryItemStats
[Export] [Export]
[Save("consumable_item_raise_vt")] [Save("consumable_item_raise_vt")]
public int PermanentRaiseVTAmount { get; set; } = 0; public int PermanentRaiseVTAmount { get; set; } = 0;
[Export]
public bool HealsStatusAilments { get; set; } = false;
} }

View File

@@ -9,6 +9,7 @@ HealHPAmount = 0
HealVTAmount = 0 HealVTAmount = 0
PermanentRaiseHPAmount = 0 PermanentRaiseHPAmount = 0
PermanentRaiseVTAmount = 0 PermanentRaiseVTAmount = 0
HealsStatusAilments = true
Name = "Ancient Capsule" Name = "Ancient Capsule"
StatDescription = "Heals all status ailments." StatDescription = "Heals all status ailments."
FlavorText = "" FlavorText = ""

View File

@@ -8,7 +8,7 @@ height = 0.725098
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_wll7p"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_wll7p"]
radius = 0.470016 radius = 0.470016
[node name="Ammo" type="RigidBody3D"] [node name="Weapon" type="RigidBody3D"]
collision_layer = 0 collision_layer = 0
axis_lock_linear_x = true axis_lock_linear_x = true
axis_lock_linear_z = true axis_lock_linear_z = true

View File

@@ -125,6 +125,11 @@ unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Die" 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"] [node name="DebugInfoCheckbox" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer/VFlowContainer"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2

View File

@@ -21,6 +21,7 @@ public partial class DummyPlayer : CharacterBody3D, IPlayer
public int HealthTimerHPRate { get; set; } public int HealthTimerHPRate { get; set; }
public float HealthTimerSpeedModifier { get; } public float HealthTimerSpeedModifier { get; }
public bool AutoIdentifyItems { get; set; } public bool AutoIdentifyItems { get; set; }
public IStatusEffectComponent StatusEffectComponent { get; }
public event Action PlayerDied; public event Action PlayerDied;

View File

@@ -37,6 +37,8 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
public IEquipmentComponent EquipmentComponent { get; private set; } public IEquipmentComponent EquipmentComponent { get; private set; }
public IStatusEffectComponent StatusEffectComponent { get; private set; }
public Vector3 CurrentPosition => GlobalPosition; public Vector3 CurrentPosition => GlobalPosition;
public Basis CurrentBasis => Transform.Basis; public Basis CurrentBasis => Transform.Basis;
@@ -70,6 +72,8 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
[Export(PropertyHint.Range, "1, 100, 1")] public int InitialLuck { get; set; } = 8; [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] [Export]
private bool HealthTimerIsActive = false; private bool HealthTimerIsActive = false;
#endregion #endregion
@@ -149,6 +153,7 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
ExperiencePointsComponent = new ExperiencePointsComponent(); ExperiencePointsComponent = new ExperiencePointsComponent();
LuckComponent = new LuckComponent(InitialLuck); LuckComponent = new LuckComponent(InitialLuck);
EquipmentComponent = new EquipmentComponent(); EquipmentComponent = new EquipmentComponent();
StatusEffectComponent = new StatusEffectComponent(RustDuration);
_itemReroller = new ItemReroller(ItemDatabase.Instance); _itemReroller = new ItemReroller(ItemDatabase.Instance);
_playerEffectService = new PlayerEffectService(this); _playerEffectService = new PlayerEffectService(this);
@@ -872,6 +877,19 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
_playerEffectService.TakeSelfDamage(weapon.Stats.SelfDamage); _playerEffectService.TakeSelfDamage(weapon.Stats.SelfDamage);
if (weapon.WeaponTag == WeaponTag.Instakill) if (weapon.WeaponTag == WeaponTag.Instakill)
_playerEffectService.Instakill(enemy); _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) private async void CollisionDetector_AreaEntered(Area3D area)

View File

@@ -26,6 +26,8 @@ public partial class PauseDebugMenu : Control, IDebugMenu
[Node] public Button LoadNextFloorButton { get; set; } = default!; [Node] public Button LoadNextFloorButton { get; set; } = default!;
[Node] public Button RustButton { get; set; } = default!;
[Node] public Button DieButton { get; set; } = default!; [Node] public Button DieButton { get; set; } = default!;
[Node] public CheckBox DebugInfoCheckbox { get; set; } = default!; [Node] public CheckBox DebugInfoCheckbox { get; set; } = default!;
@@ -42,6 +44,7 @@ public partial class PauseDebugMenu : Control, IDebugMenu
{ {
LoadNextFloorButton.Pressed += LoadNextFloorButton_Pressed; LoadNextFloorButton.Pressed += LoadNextFloorButton_Pressed;
DieButton.Pressed += DieButton_Pressed; DieButton.Pressed += DieButton_Pressed;
RustButton.Pressed += RustButton_Pressed;
_itemDatabase = ItemDatabase.Instance; _itemDatabase = ItemDatabase.Instance;
_spawnableItems = _itemDatabase.Items; _spawnableItems = _itemDatabase.Items;
@@ -94,6 +97,8 @@ public partial class PauseDebugMenu : Control, IDebugMenu
private void DieButton_Pressed() => _player.Die(); private void DieButton_Pressed() => _player.Die();
private void RustButton_Pressed() => _player.StatusEffectComponent.Rust.OnNext(true);
private void FloorSelectDropDown_ItemSelected(long index) private void FloorSelectDropDown_ItemSelected(long index)
{ {
var sceneName = FloorSelectDropDown.GetItemText((int)index); var sceneName = FloorSelectDropDown.GetItemText((int)index);