From dfa8a17ba1a7fea477a779c796ad93a924c2435f Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 12 Aug 2025 12:19:18 +0200 Subject: [PATCH] Add new components: CannotStompComponent, SkillUnlockedComponent, SpaceshipEnterComponent, SpaceshipExitComponent, SpinComponent, StompDamageComponent, StraightMotionComponent, TerrainHitFx, TooltipComponent, TrailComponent, and UnlockOnRequirementComponent --- scripts/components/CannotStompComponent.cs | 8 +++ scripts/components/ExitDoorComponent.cs | 5 +- scripts/components/SkillUnlockedComponent.cs | 70 +++++++++++++++++++ scripts/components/SpaceshipEnterComponent.cs | 21 ++++++ scripts/components/SpaceshipExitComponent.cs | 20 ++++++ scripts/components/SpinComponent.cs | 23 ++++++ scripts/components/StompDamageComponent.cs | 43 ++++++++++++ scripts/components/StraightMotionComponent.cs | 19 +++++ scripts/components/TerrainHitFx.cs | 33 +++++++++ scripts/components/TooltipComponent.cs | 39 +++++++++++ scripts/components/TrailComponent.cs | 22 ++++++ .../UnlockOnRequirementComponent.cs | 25 +++++++ scripts/interfaces/IUnlockable.cs | 6 ++ 13 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 scripts/components/CannotStompComponent.cs create mode 100644 scripts/components/SkillUnlockedComponent.cs create mode 100644 scripts/components/SpaceshipEnterComponent.cs create mode 100644 scripts/components/SpaceshipExitComponent.cs create mode 100644 scripts/components/SpinComponent.cs create mode 100644 scripts/components/StompDamageComponent.cs create mode 100644 scripts/components/StraightMotionComponent.cs create mode 100644 scripts/components/TerrainHitFx.cs create mode 100644 scripts/components/TooltipComponent.cs create mode 100644 scripts/components/TrailComponent.cs create mode 100644 scripts/components/UnlockOnRequirementComponent.cs create mode 100644 scripts/interfaces/IUnlockable.cs diff --git a/scripts/components/CannotStompComponent.cs b/scripts/components/CannotStompComponent.cs new file mode 100644 index 0000000..fbc52b9 --- /dev/null +++ b/scripts/components/CannotStompComponent.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CannotStompComponent : Node +{ + +} \ No newline at end of file diff --git a/scripts/components/ExitDoorComponent.cs b/scripts/components/ExitDoorComponent.cs index 9eb2652..d204421 100644 --- a/scripts/components/ExitDoorComponent.cs +++ b/scripts/components/ExitDoorComponent.cs @@ -1,9 +1,10 @@ using Godot; using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.interfaces; namespace Mr.BrickAdventures.scripts.components; -public partial class ExitDoorComponent : Node +public partial class ExitDoorComponent : Node, IUnlockable { [Export] public bool Locked { get; set; } = true; [Export] public Area2D ExitArea { get; set; } @@ -33,7 +34,7 @@ public partial class ExitDoorComponent : Node throw new System.NotImplementedException(); } - private void Unlock() + public void Unlock() { Locked = false; if (DoorSprite != null) diff --git a/scripts/components/SkillUnlockedComponent.cs b/scripts/components/SkillUnlockedComponent.cs new file mode 100644 index 0000000..33d3aa9 --- /dev/null +++ b/scripts/components/SkillUnlockedComponent.cs @@ -0,0 +1,70 @@ +using Godot; +using Godot.Collections; +using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SkillUnlockedComponent : Node +{ + [Export] public SkillManager SkillManager { get; set; } + + [Signal] + public delegate void SkillUnlockedEventHandler(SkillData skill); + + private GameManager _gameManager; + + public override void _Ready() + { + _gameManager = GetNode("/root/GameManager"); + } + + private bool HasEnoughCoins(int amount) + { + return _gameManager != null && _gameManager.GetCoins() >= amount; + } + + public bool TryUnlockSkill(SkillData skill) + { + if (_gameManager == null) return false; + if (_gameManager.IsSkillUnlocked(skill)) return false; + if (!HasEnoughCoins(skill.Cost)) return false; + + skill.Level = 1; + skill.IsActive = true; + _gameManager.RemoveCoins(skill.Cost); + + var skillsUnlocked = (Array)_gameManager.CurrentSessionState["skills_unlocked"]; + skillsUnlocked.Add(skill); + SkillManager.AddSkill(skill); + EmitSignalSkillUnlocked(skill); + + return true; + } + + public void UnlockAllSkills() + { + var availableSkills = SkillManager.AvailableSkills; + + foreach (var skill in availableSkills) + { + EmitSignalSkillUnlocked(skill); + } + + _gameManager.UnlockSkills(availableSkills); + SkillManager.ApplyUnlockedSkills(); + } + + public bool TryUpgradeSkill(SkillData skill) + { + if (_gameManager == null) return false; + if (!_gameManager.IsSkillUnlocked(skill)) return false; + if (!HasEnoughCoins(skill.Cost)) return false; + if (skill.Level >= skill.MaxLevel) return false; + + _gameManager.RemoveCoins(skill.Cost); + skill.Level++; + EmitSignalSkillUnlocked(skill); + return true; + } +} \ No newline at end of file diff --git a/scripts/components/SpaceshipEnterComponent.cs b/scripts/components/SpaceshipEnterComponent.cs new file mode 100644 index 0000000..6eb0355 --- /dev/null +++ b/scripts/components/SpaceshipEnterComponent.cs @@ -0,0 +1,21 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SpaceshipEnterComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Signal] public delegate void SpaceshipEnteredEventHandler(); + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + if (body is not PlayerController) return; + EmitSignalSpaceshipEntered(); + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/SpaceshipExitComponent.cs b/scripts/components/SpaceshipExitComponent.cs new file mode 100644 index 0000000..4cf413f --- /dev/null +++ b/scripts/components/SpaceshipExitComponent.cs @@ -0,0 +1,20 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SpaceshipExitComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Signal] public delegate void SpaceshipExitEventHandler(); + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + if (body is not PlayerController) return; + EmitSignalSpaceshipExit(); + } +} \ No newline at end of file diff --git a/scripts/components/SpinComponent.cs b/scripts/components/SpinComponent.cs new file mode 100644 index 0000000..e099ca6 --- /dev/null +++ b/scripts/components/SpinComponent.cs @@ -0,0 +1,23 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SpinComponent : Node +{ + [Export] public float SpinSpeed { get; set; } = 8f; + [Export] public Vector2 SpinDirection { get; set; } = Vector2.Right; + + public override void _Process(double delta) + { + Spin((float)delta); + } + + private void Spin(float delta) + { + var rotationSpeed = SpinSpeed * SpinDirection.X * delta; + if (Owner is Node2D root) + { + root.Rotation += rotationSpeed; + } + } +} \ No newline at end of file diff --git a/scripts/components/StompDamageComponent.cs b/scripts/components/StompDamageComponent.cs new file mode 100644 index 0000000..9f16b26 --- /dev/null +++ b/scripts/components/StompDamageComponent.cs @@ -0,0 +1,43 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class StompDamageComponent : Node +{ + [Export] public float Damage { get; set; } = 0.25f; + [Export] public Area2D Area { get; set; } + [Export] public PlayerController Root { get; set; } + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private async void OnBodyEntered(Node2D body) + { + var health = body.GetNodeOrNull("HealthComponent"); + if (health == null) return; + + var cannotStompComponent = body.GetNodeOrNull("CannotStompComponent"); + if (cannotStompComponent != null) return; + + if (!(Root.GlobalPosition.Y < body.GlobalPosition.Y)) return; + + var velocity = Root.CurrentMovement.PreviousVelocity; + if (!(velocity.Y > 0f)) return; + + DealDamage(health); + + var damageComponent = body.GetNodeOrNull("DamageComponent"); + if (damageComponent == null) return; + + damageComponent.SetProcess(false); + await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); + damageComponent.SetProcess(true); + } + + private void DealDamage(HealthComponent target) + { + target.DecreaseHealth(Damage); + } +} \ No newline at end of file diff --git a/scripts/components/StraightMotionComponent.cs b/scripts/components/StraightMotionComponent.cs new file mode 100644 index 0000000..0e462a6 --- /dev/null +++ b/scripts/components/StraightMotionComponent.cs @@ -0,0 +1,19 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class StraightMotionComponent : Node +{ + [Export] public LaunchComponent LaunchComponent { get; set; } + + public override void _PhysicsProcess(double delta) + { + var root = Owner as Node2D; + if (root == null || LaunchComponent == null) + { + return; + } + + root.Position += LaunchComponent.GetInitialVelocity() * (float)delta; + } +} \ No newline at end of file diff --git a/scripts/components/TerrainHitFx.cs b/scripts/components/TerrainHitFx.cs new file mode 100644 index 0000000..053652b --- /dev/null +++ b/scripts/components/TerrainHitFx.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TerrainHitFx : Node +{ + private List _gpuParticles = []; + + public override void _Ready() + { + if (Owner is GpuParticles2D gpuParticle) _gpuParticles.Add(gpuParticle); + + foreach (var child in GetChildren()) + { + if (child is GpuParticles2D p) + { + _gpuParticles.Add(p); + } + } + } + + public void TriggerFx() + { + foreach (var fx in _gpuParticles.Where(fx => fx != null)) + { + fx.Restart(); + fx.Emitting = true; + } + } +} \ No newline at end of file diff --git a/scripts/components/TooltipComponent.cs b/scripts/components/TooltipComponent.cs new file mode 100644 index 0000000..f32961b --- /dev/null +++ b/scripts/components/TooltipComponent.cs @@ -0,0 +1,39 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TooltipComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Export] public Control UiRoot { get; set; } + [Export] public string Text { get; set; } = string.Empty; + [Export] public Label TooltipLabel { get; set; } + + public override void _Ready() + { + TooltipLabel.Text = Text; + UiRoot.Visible = false; + Area.BodyEntered += OnBodyEntered; + Area.BodyExited += OnBodyExited; + } + + private void OnBodyEntered(Node2D body) + { + ShowTooltip(); + } + + private void OnBodyExited(Node2D body) + { + HideTooltip(); + } + + private void ShowTooltip() + { + UiRoot.Visible = true; + } + + private void HideTooltip() + { + UiRoot.Visible = false; + } +} \ No newline at end of file diff --git a/scripts/components/TrailComponent.cs b/scripts/components/TrailComponent.cs new file mode 100644 index 0000000..16e6067 --- /dev/null +++ b/scripts/components/TrailComponent.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TrailComponent : Line2D +{ + [Export] public int MaxPoints { get; set; } = 100; + + private readonly Queue _queue = new(); + + public override void _Process(double delta) + { + if (Owner is Node2D root) _queue.Enqueue(root.GlobalPosition); + + if (_queue.Count > MaxPoints) _queue.Dequeue(); + + ClearPoints(); + + foreach (var point in _queue) AddPoint(point); + } +} \ No newline at end of file diff --git a/scripts/components/UnlockOnRequirementComponent.cs b/scripts/components/UnlockOnRequirementComponent.cs new file mode 100644 index 0000000..fa70625 --- /dev/null +++ b/scripts/components/UnlockOnRequirementComponent.cs @@ -0,0 +1,25 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class UnlockOnRequirementComponent : Node +{ + [Export] public RequirementComponent RequirementComponent { get; set; } + [Export] public Node UnlockTarget { get; set; } + + public override void _Ready() + { + RequirementComponent.RequirementMet += OnRequirementMet; + } + + private void OnRequirementMet(CollectableType requirementType) + { + if (requirementType != RequirementComponent.RequirementType) return; + if (UnlockTarget is IUnlockable unlockable) + { + unlockable.Unlock(); + } + } +} \ No newline at end of file diff --git a/scripts/interfaces/IUnlockable.cs b/scripts/interfaces/IUnlockable.cs new file mode 100644 index 0000000..b77f20b --- /dev/null +++ b/scripts/interfaces/IUnlockable.cs @@ -0,0 +1,6 @@ +namespace Mr.BrickAdventures.scripts.interfaces; + +public interface IUnlockable +{ + void Unlock(); +} \ No newline at end of file