Add new UID files and update scene configurations for dialogue components; refactor skill management and input handling
This commit is contained in:
@@ -8,7 +8,6 @@ public partial class SkillData : Resource
|
||||
{
|
||||
[Export] public string Name { get; set; } = "New Skill";
|
||||
[Export] public string Description { get; set; } = "New Skill";
|
||||
[Export] public Dictionary<string, Variant> Config { get; set; } = new();
|
||||
[Export] public int Cost { get; set; } = 0;
|
||||
[Export] public Texture2D Icon { get; set; }
|
||||
[Export] public bool IsActive { get; set; } = false;
|
||||
|
@@ -1,13 +1,13 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public partial class TapThrowInputResource : ThrowInputResource
|
||||
{
|
||||
public override void Update(double delta)
|
||||
|
||||
public override void ProcessInput(InputEvent @event)
|
||||
{
|
||||
if (Input.IsActionPressed("attack"))
|
||||
if (@event.IsActionPressed("attack"))
|
||||
{
|
||||
EmitSignalThrowRequested(1f);
|
||||
}
|
||||
|
@@ -9,16 +9,14 @@ public abstract partial class ThrowInputResource : Resource, IThrowInput
|
||||
|
||||
public virtual void ProcessInput(InputEvent @event)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual void Update(double delta)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual bool SupportsCharging()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Mr.BrickAdventures.Autoloads;
|
||||
using Mr.BrickAdventures.scripts.interfaces;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts;
|
||||
@@ -26,44 +27,33 @@ public partial class SkillManager : Node
|
||||
if (skillData.Type == SkillType.Throw)
|
||||
{
|
||||
var unlocked = _gameManager.GetUnlockedSkills();
|
||||
foreach (var skill in unlocked)
|
||||
foreach (var sd in unlocked)
|
||||
{
|
||||
SkillData data = null;
|
||||
foreach (var s in AvailableSkills)
|
||||
{
|
||||
if (s == (SkillData)skill)
|
||||
if (s == sd)
|
||||
{
|
||||
data = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data != null && data.Type == SkillType.Throw)
|
||||
if (data is { Type: SkillType.Throw })
|
||||
RemoveSkill(data.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var instance = skillData.Node.Instantiate();
|
||||
foreach (var key in skillData.Config.Keys)
|
||||
if (instance is ISkill skill)
|
||||
{
|
||||
if (instance.HasMethod("get")) // rough presence check
|
||||
{
|
||||
var value = skillData.Config[key];
|
||||
var parent = GetParent();
|
||||
|
||||
if (value.VariantType == Variant.Type.NodePath)
|
||||
{
|
||||
var np = (NodePath)value;
|
||||
if (parent.HasNode(np))
|
||||
value = parent.GetNode(np);
|
||||
else if (instance.HasNode(np))
|
||||
value = instance.GetNode(np);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set via property if exists
|
||||
instance.Set(key, value);
|
||||
}
|
||||
skill.Initialize(Owner);
|
||||
skill.Activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"Skill scene for '{skillData.Name}' does not implement ISkill!");
|
||||
instance.QueueFree();
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.AddChild(instance);
|
||||
@@ -76,6 +66,11 @@ public partial class SkillManager : Node
|
||||
return;
|
||||
|
||||
var inst = (Node)component;
|
||||
if (inst is ISkill skill)
|
||||
{
|
||||
skill.Deactivate();
|
||||
}
|
||||
|
||||
if (IsInstanceValid(inst))
|
||||
inst.QueueFree();
|
||||
|
||||
|
@@ -13,7 +13,7 @@ public partial class Marketplace : Control
|
||||
[Export] public GridContainer ToUnlockGrid { get; set; }
|
||||
[Export] public GridContainer UnlockedGrid { get; set; }
|
||||
[Export] public Font Font { get; set; }
|
||||
[Export] public SkillUnlockedComponent SkillUnlockedComponent { get; set; }
|
||||
[Export] public SkillUnlockerComponent SkillUnlockerComponent { get; set; }
|
||||
[Export] public Array<Node> ComponentsToDisable { get; set; } = [];
|
||||
[Export] public PackedScene MarketplaceButtonScene { get; set; }
|
||||
[Export] public PackedScene SkillButtonScene { get; set; }
|
||||
@@ -35,12 +35,12 @@ public partial class Marketplace : Control
|
||||
var unlockedSkills = _gameManager.GetUnlockedSkills();
|
||||
foreach (var skill in unlockedSkills) CreateSkillButton(skill);
|
||||
|
||||
SkillUnlockedComponent.SkillUnlocked += OnSkillUnlocked;
|
||||
SkillUnlockerComponent.SkillUnlocked += OnSkillUnlocker;
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
SkillUnlockedComponent.SkillUnlocked -= OnSkillUnlocked;
|
||||
SkillUnlockerComponent.SkillUnlocked -= OnSkillUnlocker;
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
@@ -64,7 +64,7 @@ public partial class Marketplace : Control
|
||||
return $"{Tr(skill.Name)} {skill.Cost}";
|
||||
}
|
||||
|
||||
private void OnSkillUnlocked(SkillData skill)
|
||||
private void OnSkillUnlocker(SkillData skill)
|
||||
{
|
||||
if (_skillButtons.Count == 0) CreateSkillButton(skill);
|
||||
|
||||
@@ -109,17 +109,17 @@ public partial class Marketplace : Control
|
||||
{
|
||||
if (skill.Level < skill.MaxLevel)
|
||||
{
|
||||
SkillUnlockedComponent.TryUpgradeSkill(skill);
|
||||
if (!skill.IsActive) SkillUnlockedComponent.SkillManager.ToggleSkillActivation(skill);
|
||||
SkillUnlockerComponent.TryUpgradeSkill(skill);
|
||||
if (!skill.IsActive) SkillUnlockerComponent.SkillManager.ToggleSkillActivation(skill);
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillUnlockedComponent.SkillManager.ToggleSkillActivation(skill);
|
||||
SkillUnlockerComponent.SkillManager.ToggleSkillActivation(skill);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillUnlockedComponent.TryUnlockSkill(skill);
|
||||
SkillUnlockerComponent.TryUnlockSkill(skill);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,6 @@ public partial class Marketplace : Control
|
||||
|
||||
private void OnSkillButtonPressed(SkillButton button)
|
||||
{
|
||||
SkillUnlockedComponent.SkillManager.ToggleSkillActivation(button.Data);
|
||||
SkillUnlockerComponent.SkillManager.ToggleSkillActivation(button.Data);
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ public partial class MarketplaceButton : Button
|
||||
[Export] public Container SkillLevelContainer { get; set; }
|
||||
|
||||
private GameManager _gameManager;
|
||||
private SkillUnlockedComponent _skillUnlockedComponent;
|
||||
private SkillUnlockerComponent _skillUnlockerComponent;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
@@ -22,7 +22,7 @@ public partial class MarketplaceButton : Button
|
||||
Setup();
|
||||
var player = _gameManager.Player;
|
||||
|
||||
var skillUnlockerComponent = player?.GetNodeOrNull<SkillUnlockedComponent>("SkillUnlockerComponent");
|
||||
var skillUnlockerComponent = player?.GetNodeOrNull<SkillUnlockerComponent>("SkillUnlockerComponent");
|
||||
if (skillUnlockerComponent == null) return;
|
||||
|
||||
skillUnlockerComponent.SkillUnlocked += OnSkillUnlock;
|
||||
@@ -30,7 +30,7 @@ public partial class MarketplaceButton : Button
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_skillUnlockedComponent.SkillUnlocked -= OnSkillUnlock;
|
||||
_skillUnlockerComponent.SkillUnlocked -= OnSkillUnlock;
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
|
@@ -4,7 +4,7 @@ using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class BrickThrowComponent : Node
|
||||
public partial class BrickThrowComponent : Node, ISkill
|
||||
{
|
||||
[Export] public PackedScene BrickScene { get; set; }
|
||||
[Export] public float FireRate { get; set; } = 1.0f;
|
||||
@@ -18,8 +18,16 @@ public partial class BrickThrowComponent : Node
|
||||
{
|
||||
SetupTimer();
|
||||
_canThrow = true;
|
||||
}
|
||||
|
||||
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested += ThrowBrick;
|
||||
public override void _ExitTree()
|
||||
{
|
||||
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested -= ThrowBrick;
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Timeout -= OnTimerTimeout;
|
||||
_timer.QueueFree();
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
@@ -34,10 +42,12 @@ public partial class BrickThrowComponent : Node
|
||||
|
||||
private void SetupTimer()
|
||||
{
|
||||
_timer = new Timer();
|
||||
_timer.WaitTime = FireRate;
|
||||
_timer.OneShot = false;
|
||||
_timer.Autostart = false;
|
||||
_timer.Timeout += OnTimerTimeout;
|
||||
AddChild(_timer);
|
||||
}
|
||||
|
||||
private void OnTimerTimeout()
|
||||
@@ -52,19 +62,41 @@ public partial class BrickThrowComponent : Node
|
||||
|
||||
var instance = BrickScene.Instantiate<Node2D>();
|
||||
var init = instance.GetNodeOrNull<ProjectileInitComponent>("ProjectileInitComponent");
|
||||
|
||||
if (init != null && PlayerController.CurrentMovement is PlatformMovementComponent)
|
||||
{
|
||||
init.Initialize(new ProjectileInitParams()
|
||||
var @params = new ProjectileInitParams()
|
||||
{
|
||||
Position = PlayerController.GlobalPosition,
|
||||
Rotation = PlayerController.Rotation,
|
||||
Direction = PlayerController.CurrentMovement.LastDirection,
|
||||
PowerMultiplier = powerMultiplier,
|
||||
});
|
||||
};
|
||||
|
||||
init.Initialize(@params);
|
||||
}
|
||||
|
||||
GetTree().CurrentScene.AddChild(instance);
|
||||
_canThrow = false;
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public void Initialize(Node owner)
|
||||
{
|
||||
PlayerController = owner as PlayerController;
|
||||
if (PlayerController == null)
|
||||
{
|
||||
GD.PushError("BrickThrowComponent: Owner is not a PlayerController.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested += ThrowBrick;
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested -= ThrowBrick;
|
||||
}
|
||||
}
|
@@ -16,13 +16,13 @@ public partial class ExplosiveComponent : Node2D
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (Damage != null)
|
||||
if (Damage == null)
|
||||
{
|
||||
GD.PushError("ExplosiveComponent: DamageComponent is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExplodeArea != null)
|
||||
if (ExplodeArea == null)
|
||||
{
|
||||
GD.PushError("ExplosiveComponent: ExplodeArea is not set.");
|
||||
return;
|
||||
@@ -30,6 +30,8 @@ public partial class ExplosiveComponent : Node2D
|
||||
|
||||
Area.BodyEntered += OnAreaBodyEntered;
|
||||
Area.AreaEntered += OnAreaAreaEntered;
|
||||
|
||||
PrepareTimer();
|
||||
}
|
||||
|
||||
private void OnAreaAreaEntered(Area2D area)
|
||||
|
@@ -15,9 +15,9 @@ public partial class GravityMotionComponent : Node2D
|
||||
{
|
||||
if (LaunchComponent == null) return;
|
||||
|
||||
var direction = LaunchComponent.InitialDirection.X > 0f ? TargetDirection : new Vector2(-TargetDirection.X, TargetDirection.Y);
|
||||
direction = direction.Normalized();
|
||||
_velocity = direction * LaunchComponent.Speed;
|
||||
var horizontalDirection = LaunchComponent.InitialDirection.Normalized();
|
||||
var combinedDirection = (horizontalDirection + TargetDirection).Normalized();
|
||||
_velocity = combinedDirection * LaunchComponent.Speed;
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
|
@@ -1,21 +1,17 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class MagneticSkillComponent : Node
|
||||
public partial class MagneticSkillComponent : Node, ISkill
|
||||
{
|
||||
[Export] public Area2D MagneticArea { get; set; }
|
||||
[Export] public float MagneticMoveDuration { get; set; } = 1.25f;
|
||||
|
||||
private Array<Node2D> _collectablesToPickUp = [];
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
MagneticArea.AreaEntered += OnAreaEntered;
|
||||
MagneticArea.BodyEntered += OnBodyEntered;
|
||||
}
|
||||
private Node2D _owner;
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
@@ -66,13 +62,32 @@ public partial class MagneticSkillComponent : Node
|
||||
|
||||
private void MoveCollectableToOwner(Node2D collectable)
|
||||
{
|
||||
if (!IsInstanceValid(collectable)) return;
|
||||
|
||||
if (Owner is not Node2D root) return;
|
||||
if (!IsInstanceValid(collectable) || !IsInstanceValid(_owner)) return;
|
||||
|
||||
var direction = (root.GlobalPosition - collectable.GlobalPosition).Normalized();
|
||||
var direction = (_owner.GlobalPosition - collectable.GlobalPosition).Normalized();
|
||||
var speed = direction.Length() / MagneticMoveDuration;
|
||||
|
||||
collectable.GlobalPosition += direction.Normalized() * speed;
|
||||
}
|
||||
|
||||
public void Initialize(Node owner)
|
||||
{
|
||||
_owner = owner as Node2D;
|
||||
if (_owner == null)
|
||||
{
|
||||
GD.PushWarning("MagneticSkillComponent: Owner is not a Node2D.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
MagneticArea.BodyEntered += OnBodyEntered;
|
||||
MagneticArea.AreaEntered += OnAreaEntered;
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
MagneticArea.BodyEntered -= OnBodyEntered;
|
||||
MagneticArea.AreaEntered -= OnAreaEntered;
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class ProjectileComponent : Node2D
|
||||
{
|
||||
[Export] public float Speed { get; set; } = 16f;
|
||||
|
@@ -2,7 +2,7 @@ using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ProjectileInitParams
|
||||
public class ProjectileInitParams
|
||||
{
|
||||
public Vector2 Position { get; set; } = Vector2.Zero;
|
||||
public Vector2 Direction { get; set; } = Vector2.Right;
|
||||
@@ -20,7 +20,7 @@ public partial class ProjectileInitComponent : Node
|
||||
var direction = p.Direction;
|
||||
var rotation = p.Rotation;
|
||||
var power = p.PowerMultiplier;
|
||||
|
||||
|
||||
if (Owner is Node2D root)
|
||||
{
|
||||
root.GlobalPosition = position;
|
||||
|
@@ -5,7 +5,7 @@ using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class SkillUnlockedComponent : Node
|
||||
public partial class SkillUnlockerComponent : Node
|
||||
{
|
||||
[Export] public SkillManager SkillManager { get; set; }
|
||||
|
@@ -1,64 +0,0 @@
|
||||
# class_name BeamComponent
|
||||
extends Node2D
|
||||
|
||||
@export var expansion_speed: float = 16.0
|
||||
@export var max_length: float = 512.0
|
||||
@export var direction: Vector2 = Vector2.DOWN
|
||||
|
||||
var current_length: float = 16.0
|
||||
|
||||
@export var root: Node2D
|
||||
@export var sprite2d: Sprite2D
|
||||
@export var collision_shape: CollisionShape2D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
collision_shape.shape.extents = Vector2(current_length / 2.0, current_length / 2.0)
|
||||
sprite2d.scale = Vector2(1, 1)
|
||||
collision_shape.position = Vector2.ZERO
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var new_length = current_length + expansion_speed * delta
|
||||
if new_length > max_length:
|
||||
new_length = max_length
|
||||
|
||||
if not check_for_obstacle(new_length):
|
||||
expand_beam(new_length)
|
||||
|
||||
|
||||
func expand_beam(new_length: float) -> void:
|
||||
current_length = new_length
|
||||
|
||||
if direction == Vector2.UP:
|
||||
sprite2d.scale.y = current_length / 16.0
|
||||
sprite2d.position.y = -current_length / 2.0
|
||||
collision_shape.shape.extents = Vector2(8.0, current_length / 2.0)
|
||||
collision_shape.position.y = -current_length / 2.0
|
||||
elif direction == Vector2.DOWN:
|
||||
sprite2d.scale.y = current_length / 16.0
|
||||
sprite2d.position.y = current_length / 2.0
|
||||
collision_shape.shape.extents = Vector2(8.0, current_length / 2.0)
|
||||
collision_shape.position.y = current_length / 2.0
|
||||
elif direction == Vector2.LEFT:
|
||||
sprite2d.scale.y = current_length / 16.0
|
||||
sprite2d.position.x = -current_length / 2.0
|
||||
collision_shape.shape.extents = Vector2(current_length / 2.0, 8.0)
|
||||
collision_shape.position.x = -current_length / 2.0
|
||||
elif direction == Vector2.RIGHT:
|
||||
sprite2d.scale.y = current_length / 16.0
|
||||
sprite2d.position.x = current_length / 2.0
|
||||
collision_shape.shape.extents = Vector2(current_length / 2.0, 8.0)
|
||||
collision_shape.position.x = current_length / 2.0
|
||||
|
||||
|
||||
func check_for_obstacle(new_length: float) -> bool:
|
||||
var space_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state
|
||||
var query_start: Vector2 = global_position
|
||||
var query_end: Vector2 = global_position + direction * new_length
|
||||
var query: PhysicsRayQueryParameters2D = PhysicsRayQueryParameters2D.create(query_start, query_end)
|
||||
query.collide_with_areas = false
|
||||
query.collide_with_bodies = true
|
||||
|
||||
var result: Dictionary = space_state.intersect_ray(query)
|
||||
return result.size() > 0
|
@@ -1 +0,0 @@
|
||||
uid://bejv75mi8npj0
|
@@ -1,59 +0,0 @@
|
||||
class_name BrickThrowComponent
|
||||
extends Node
|
||||
|
||||
@export var brick_scene: PackedScene
|
||||
@export var fire_rate: float = 1.0
|
||||
@export var player_controller: PlayerController
|
||||
@export var timer: Timer
|
||||
@export var throw_input_behavior: ThrowInputResource
|
||||
|
||||
var can_throw: bool = true
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
setup_timer()
|
||||
can_throw = true
|
||||
|
||||
if throw_input_behavior:
|
||||
throw_input_behavior.throw_requested.connect(throw_brick)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if throw_input_behavior:
|
||||
throw_input_behavior.process_input(event)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if throw_input_behavior:
|
||||
throw_input_behavior.update(delta)
|
||||
|
||||
|
||||
func setup_timer() -> void:
|
||||
timer.wait_time = fire_rate
|
||||
timer.one_shot = false
|
||||
timer.autostart = false
|
||||
timer.timeout.connect(on_timer_timeout)
|
||||
|
||||
|
||||
func on_timer_timeout() -> void:
|
||||
can_throw = true
|
||||
|
||||
|
||||
func throw_brick(power_multiplier: float = 1.0) -> void:
|
||||
if not can_throw:
|
||||
return
|
||||
|
||||
var instance: Node2D = brick_scene.instantiate()
|
||||
var init := instance.get_node_or_null("ProjectileInitComponent") as ProjectileInitComponent
|
||||
if init and player_controller.current_movement is PlatformMovement:
|
||||
init.initialize({
|
||||
"position": player_controller.global_position,
|
||||
"rotation": player_controller.rotation,
|
||||
"direction": player_controller.current_movement.last_direction,
|
||||
"power_multiplier": power_multiplier
|
||||
})
|
||||
|
||||
get_tree().current_scene.add_child(instance)
|
||||
|
||||
can_throw = false
|
||||
timer.start()
|
@@ -1 +0,0 @@
|
||||
uid://cm06xg1l3xtw5
|
@@ -1,37 +0,0 @@
|
||||
class_name BulletComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node2D
|
||||
@export var area2d: Area2D
|
||||
@export var hit_terrain_fx: TerrainHitFx
|
||||
@export var bullet_sprite: Sprite2D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
area2d.body_entered.connect(on_area2d_body_entered)
|
||||
area2d.area_entered.connect(on_area2d_area_entered)
|
||||
|
||||
|
||||
|
||||
func on_area2d_body_entered(body: Node2D) -> void:
|
||||
if body is TileMapLayer:
|
||||
if bullet_sprite:
|
||||
bullet_sprite.visible = false
|
||||
play_terrain_hit_fx()
|
||||
return
|
||||
|
||||
root.queue_free()
|
||||
|
||||
|
||||
func on_area2d_area_entered(_area: Area2D) -> void:
|
||||
root.queue_free()
|
||||
|
||||
|
||||
|
||||
func play_terrain_hit_fx() -> void:
|
||||
if not hit_terrain_fx:
|
||||
return
|
||||
|
||||
await hit_terrain_fx.trigger_fx()
|
||||
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://cdnwrn8v05qhi
|
@@ -1,37 +0,0 @@
|
||||
class_name CageComponent
|
||||
extends Node
|
||||
|
||||
@export var lever: LeverComponent
|
||||
@export var root: Node2D
|
||||
@export var move_value: Vector2 = Vector2(0, -100)
|
||||
@export var tween_duration: float = 0.5
|
||||
@export var should_free: bool = true
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
if not lever:
|
||||
var levers_nodes := get_tree().get_nodes_in_group("levers")
|
||||
for lever_node in levers_nodes:
|
||||
var lever_component: LeverComponent = lever_node.get_node_or_null("LeverComponent")
|
||||
if lever_component:
|
||||
lever_component.activated.connect(on_lever_activated)
|
||||
else:
|
||||
lever.activated.connect(on_lever_activated)
|
||||
|
||||
if not root:
|
||||
printerr("CageComponent: root is not set.")
|
||||
return
|
||||
|
||||
|
||||
func on_lever_activated() -> void:
|
||||
var tween: Tween = create_tween()
|
||||
var end_position: Vector2 = root.position + move_value
|
||||
tween.tween_property(root, "position", end_position, tween_duration)
|
||||
tween.tween_callback(_on_tween_completed)
|
||||
|
||||
|
||||
func _on_tween_completed() -> void:
|
||||
if not should_free:
|
||||
return
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://ddplvyjqguxtl
|
@@ -1,2 +0,0 @@
|
||||
class_name CanBeLaunchedComponent
|
||||
extends Node
|
@@ -1 +0,0 @@
|
||||
uid://6ffxsx3gknhr
|
@@ -1,2 +0,0 @@
|
||||
class_name CanPickUpComponent
|
||||
extends Node
|
@@ -1 +0,0 @@
|
||||
uid://dcvjvjy2nhf8s
|
@@ -1,2 +0,0 @@
|
||||
class_name CannotStompComponent
|
||||
extends Node
|
@@ -1 +0,0 @@
|
||||
uid://ct7lsuwjvmwxu
|
@@ -1,59 +0,0 @@
|
||||
class_name ChargeThrowComponent
|
||||
extends Node
|
||||
|
||||
@export var min_charge_duration: float = 0.1
|
||||
@export var min_power: float = 0.5
|
||||
@export var max_power: float = 2.0
|
||||
@export var max_charge_time: float = 1.5
|
||||
|
||||
var charge_start_time: float = 0.0
|
||||
var is_charging: bool = false
|
||||
signal charge_started
|
||||
signal charge_updated(charge_ratio: float)
|
||||
signal charge_stopped
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not is_charging:
|
||||
return
|
||||
|
||||
var charge_ratio := get_charge_ratio()
|
||||
charge_updated.emit(charge_ratio)
|
||||
|
||||
|
||||
func start_charging() -> void:
|
||||
is_charging = true
|
||||
charge_start_time = Time.get_ticks_msec() / 1000.0
|
||||
call_deferred("emit_charge_started")
|
||||
|
||||
|
||||
func get_charge_ratio() -> float:
|
||||
if not is_charging:
|
||||
return 0.0
|
||||
|
||||
var held_time := (Time.get_ticks_msec() / 1000.0) - charge_start_time
|
||||
var t = clamp(held_time / max_charge_time, 0.0, 1.0)
|
||||
|
||||
return lerp(min_power, max_power, t)
|
||||
|
||||
|
||||
func stop_charging() -> float:
|
||||
if not is_charging:
|
||||
return min_power
|
||||
|
||||
var held_time := (Time.get_ticks_msec() / 1000.0) - charge_start_time
|
||||
is_charging = false
|
||||
charge_start_time = 0.0
|
||||
charge_stopped.emit()
|
||||
|
||||
if held_time < min_charge_duration:
|
||||
return min_power
|
||||
|
||||
var t = clamp(held_time / max_charge_time, 0.0, 1.0)
|
||||
return lerp(min_power, max_power, t)
|
||||
|
||||
|
||||
func emit_charge_started() -> void:
|
||||
if not is_charging:
|
||||
return
|
||||
charge_started.emit()
|
@@ -1 +0,0 @@
|
||||
uid://c6fclevp3peuo
|
@@ -1,69 +0,0 @@
|
||||
class_name ChaseLevelComponent
|
||||
extends Node
|
||||
|
||||
@export var chase_speed: float = 200.0
|
||||
@export var chase_target: Marker2D
|
||||
@export var phantom_camera: PhantomCamera2D
|
||||
@export var minimum_distance: float = 10.0
|
||||
signal chase_started
|
||||
signal chase_stopped
|
||||
var is_chasing: bool = false
|
||||
var previous_camera_follow_target: Node2D = null
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not is_chasing:
|
||||
return
|
||||
|
||||
if not chase_target:
|
||||
printerr("ChaseLevelComponent: chase_target is not set.")
|
||||
return
|
||||
|
||||
if check_if_reached_target():
|
||||
stop_chasing()
|
||||
return
|
||||
|
||||
var target_position: Vector2 = chase_target.global_position
|
||||
var current_position: Vector2 = owner.global_position
|
||||
var direction: Vector2 = (target_position - current_position).normalized()
|
||||
|
||||
owner.global_position += direction * chase_speed * delta
|
||||
|
||||
|
||||
func on_ship_entered() -> void:
|
||||
if not chase_target:
|
||||
printerr("ChaseLevelComponent: chase_target is not set.")
|
||||
return
|
||||
|
||||
if not phantom_camera:
|
||||
printerr("ChaseLevelComponent: phantom_camera is not set.")
|
||||
return
|
||||
|
||||
previous_camera_follow_target = phantom_camera.get_follow_target()
|
||||
phantom_camera.set_follow_target(owner as Node2D)
|
||||
chase_started.emit()
|
||||
is_chasing = true
|
||||
|
||||
|
||||
func on_ship_exited() -> void:
|
||||
stop_chasing()
|
||||
|
||||
|
||||
func check_if_reached_target() -> bool:
|
||||
if not chase_target:
|
||||
printerr("ChaseLevelComponent: chase_target is not set.")
|
||||
return false
|
||||
|
||||
var target_position: Vector2 = chase_target.global_position
|
||||
var current_position: Vector2 = owner.global_position
|
||||
return current_position.distance_to(target_position) < minimum_distance
|
||||
|
||||
|
||||
func stop_chasing() -> void:
|
||||
if not phantom_camera:
|
||||
printerr("ChaseLevelComponent: phantom_camera is not set.")
|
||||
return
|
||||
|
||||
phantom_camera.set_follow_target(previous_camera_follow_target)
|
||||
chase_stopped.emit()
|
||||
is_chasing = false
|
@@ -1 +0,0 @@
|
||||
uid://cf4li7whw5old
|
@@ -1,8 +0,0 @@
|
||||
class_name CleanUpComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node
|
||||
|
||||
|
||||
func clean_up() -> void:
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://f74xpfg7624d
|
@@ -1,55 +0,0 @@
|
||||
class_name CollapsableComponent
|
||||
extends Node
|
||||
|
||||
@export var to_collapse_timer: Timer
|
||||
@export var reset_timer: Timer
|
||||
@export var sprite2d: Sprite2D
|
||||
@export var collision_shape: CollisionShape2D
|
||||
|
||||
@export var collapse_time: float = 0.5
|
||||
@export var reset_time: float = 1.0
|
||||
|
||||
@export var anim_time: float = 0.25
|
||||
|
||||
func _ready() -> void:
|
||||
reset_timers()
|
||||
|
||||
func _on_to_collapse_timer_timeout() -> void:
|
||||
collapse_bridge()
|
||||
|
||||
func _on_reset_timer_timeout() -> void:
|
||||
reactivate_bridge()
|
||||
|
||||
func collapse_bridge():
|
||||
to_collapse_timer.stop()
|
||||
to_collapse_timer.wait_time = collapse_time
|
||||
|
||||
var bridge_tween = create_tween()
|
||||
bridge_tween.tween_property(sprite2d, "modulate:a", 0, anim_time)
|
||||
await bridge_tween.finished
|
||||
|
||||
collision_shape.disabled = true
|
||||
reset_timer.start()
|
||||
|
||||
func reactivate_bridge():
|
||||
reset_timer.stop()
|
||||
reset_timer.wait_time = reset_time
|
||||
|
||||
var bridge_tween = create_tween()
|
||||
bridge_tween.tween_property(sprite2d, "modulate:a", 1, anim_time)
|
||||
await bridge_tween.finished
|
||||
|
||||
collision_shape.disabled = false
|
||||
|
||||
func _on_collapsable_detector_body_entered(_body: Node2D) -> void:
|
||||
to_collapse_timer.start()
|
||||
|
||||
func reset_timers():
|
||||
to_collapse_timer.stop()
|
||||
to_collapse_timer.wait_time = collapse_time
|
||||
|
||||
|
||||
func _on_collapsable_detector_body_exited(_body: Node2D) -> void:
|
||||
var collapse_time_left: float = abs(to_collapse_timer.time_left - collapse_time)
|
||||
if collapse_time_left < (0.1 * collapse_time):
|
||||
reset_timers()
|
@@ -1 +0,0 @@
|
||||
uid://r0a6xjicrh74
|
@@ -1,38 +0,0 @@
|
||||
class_name CollectableComponent
|
||||
extends Node
|
||||
|
||||
var root: Node
|
||||
var has_fade_away: bool = false
|
||||
|
||||
@export var area2d: Area2D
|
||||
@export var collision_shape: CollisionShape2D
|
||||
@export var collectable_data: CollectableResource
|
||||
@export var sfx: AudioStreamPlayer2D
|
||||
signal collected(amount: Variant, type: CollectableResource.CollectableType, body: Node2D)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if area2d:
|
||||
area2d.body_entered.connect(_on_area2d_body_entered)
|
||||
else:
|
||||
printerr("Collectable node missing Area2D child.")
|
||||
|
||||
root = get_parent()
|
||||
|
||||
if root.has_node("FadeAwayComponent"):
|
||||
has_fade_away = true
|
||||
|
||||
|
||||
func _on_area2d_body_entered(body: Node2D) -> void:
|
||||
if body.has_node("CanPickUpComponent"):
|
||||
collected.emit(collectable_data.amount, collectable_data.type, body)
|
||||
if collision_shape:
|
||||
collision_shape.call_deferred("set_disabled", true)
|
||||
if sfx:
|
||||
sfx.play()
|
||||
if not has_fade_away and sfx:
|
||||
await sfx.finished
|
||||
root.queue_free()
|
||||
elif not has_fade_away:
|
||||
root.queue_free()
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://pa1bwc4no08q
|
@@ -1,94 +0,0 @@
|
||||
class_name DamageComponent
|
||||
extends Node
|
||||
|
||||
@export var damage: float = 0.25
|
||||
@export var area2d: Area2D
|
||||
@export var status_effect_data: StatusEffectDataResource
|
||||
@export var damage_timer: Timer
|
||||
|
||||
var current_target: Node = null
|
||||
signal effect_inflicted(target: Node2D, effect: StatusEffectDataResource)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not area2d:
|
||||
printerr("No area2d assigned!")
|
||||
return
|
||||
|
||||
area2d.body_entered.connect(on_area2d_body_entered)
|
||||
area2d.body_exited.connect(on_area2d_body_exited)
|
||||
area2d.area_entered.connect(on_area2d_area_entered)
|
||||
|
||||
if damage_timer:
|
||||
damage_timer.timeout.connect(on_damage_timer_timeout)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not current_target:
|
||||
return
|
||||
if damage_timer:
|
||||
return
|
||||
process_entity_and_apply_damage(current_target)
|
||||
|
||||
|
||||
func deal_damage(target: HealthComponent) -> void:
|
||||
target.decrease_health(damage)
|
||||
|
||||
|
||||
func on_damage_timer_timeout() -> void:
|
||||
if not current_target:
|
||||
return
|
||||
|
||||
process_entity_and_apply_damage(current_target)
|
||||
|
||||
|
||||
func process_entity_and_apply_damage(body: Node2D) -> void:
|
||||
if body.has_node("HealthComponent"):
|
||||
var health_component: HealthComponent = body.get_node("HealthComponent")
|
||||
var invulnerability_component: InvulnerabilityComponent = body.get_node_or_null("InvulnerabilityComponent")
|
||||
|
||||
if invulnerability_component and invulnerability_component.is_invulnerable():
|
||||
return
|
||||
|
||||
if status_effect_data and status_effect_data.effect_type != StatusEffectComponent.EffectType.NONE:
|
||||
effect_inflicted.emit(body, status_effect_data)
|
||||
|
||||
deal_damage(health_component)
|
||||
|
||||
if invulnerability_component:
|
||||
invulnerability_component.activate()
|
||||
|
||||
|
||||
func on_area2d_body_entered(body: Node2D) -> void:
|
||||
current_target = body
|
||||
|
||||
if not check_if_processing_is_on():
|
||||
return
|
||||
|
||||
if damage_timer:
|
||||
damage_timer.start()
|
||||
|
||||
process_entity_and_apply_damage(body)
|
||||
|
||||
|
||||
func on_area2d_body_exited(body: Node2D) -> void:
|
||||
if body == current_target:
|
||||
current_target = null
|
||||
if damage_timer:
|
||||
damage_timer.stop()
|
||||
|
||||
|
||||
func on_area2d_area_entered(area: Area2D) -> void:
|
||||
if not check_if_processing_is_on():
|
||||
return
|
||||
|
||||
if area == area2d:
|
||||
return
|
||||
|
||||
var parent := area.get_parent()
|
||||
if parent.has_node("DamageComponent"):
|
||||
process_entity_and_apply_damage(parent)
|
||||
|
||||
|
||||
func check_if_processing_is_on() -> bool:
|
||||
return self.process_mode == PROCESS_MODE_INHERIT or self.process_mode == PROCESS_MODE_ALWAYS
|
@@ -1 +0,0 @@
|
||||
uid://dkmxhjtmu5xlb
|
@@ -1,23 +0,0 @@
|
||||
class_name DestroyableComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node
|
||||
@export var health_component: HealthComponent
|
||||
@export var destroy_effect: PackedScene
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not health_component:
|
||||
printerr("No health component assigned!")
|
||||
return
|
||||
|
||||
health_component.on_death.connect(on_health_component_death)
|
||||
|
||||
|
||||
func on_health_component_death() -> void:
|
||||
if destroy_effect:
|
||||
var effect: Node2D = destroy_effect.instantiate()
|
||||
health_component.get_parent().add_child(effect)
|
||||
effect.global_position = health_component.global_position
|
||||
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://d01dmoafptl2p
|
@@ -1,20 +0,0 @@
|
||||
class_name EffectInflictorComponent
|
||||
extends Node
|
||||
|
||||
@export var damage: DamageComponent
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not damage:
|
||||
printerr("No damage component assigned!")
|
||||
return
|
||||
|
||||
damage.effect_inflicted.connect(on_effect_inflicted)
|
||||
|
||||
|
||||
func on_effect_inflicted(target: Node2D, effect: StatusEffectDataResource) -> void:
|
||||
var status_effect_component: StatusEffectComponent = target.get_node_or_null("StatusEffectComponent")
|
||||
if not status_effect_component:
|
||||
return
|
||||
|
||||
status_effect_component.apply_effect(effect)
|
@@ -1 +0,0 @@
|
||||
uid://d3brcje121krs
|
@@ -1,28 +0,0 @@
|
||||
class_name EnemyDeathComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node2D
|
||||
@export var tween_duration: float = 0.5
|
||||
@export var collision_shape_2d: CollisionShape2D
|
||||
@export var health_component: HealthComponent
|
||||
|
||||
func _ready() -> void:
|
||||
if not collision_shape_2d:
|
||||
printerr("No CollisionShape2D assigned!")
|
||||
return
|
||||
if not health_component:
|
||||
printerr("No HealthComponent assigned!")
|
||||
return
|
||||
|
||||
health_component.on_death.connect(_on_health_component_on_death)
|
||||
|
||||
func _on_health_component_on_death() -> void:
|
||||
call_deferred("die")
|
||||
|
||||
|
||||
func die() -> void:
|
||||
collision_shape_2d.disabled = true
|
||||
var tween := create_tween()
|
||||
tween.tween_property(root, "scale", Vector2(0, 0), tween_duration)
|
||||
await (tween.finished)
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://dqm371fysuk7i
|
@@ -1,37 +0,0 @@
|
||||
class_name EnemyWaveTrigger
|
||||
extends Node
|
||||
|
||||
@export var area2d: Area2D
|
||||
@export var path_follow_node: PathFollow2D
|
||||
@export var speed: float = 100.0
|
||||
@export var loop: bool = false
|
||||
@export var activate_on_enter: bool = true
|
||||
|
||||
var active: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
area2d.body_entered.connect(_on_body_entered)
|
||||
if path_follow_node:
|
||||
path_follow_node.progress = 0
|
||||
path_follow_node.set_process(false)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not active or not path_follow_node:
|
||||
return
|
||||
path_follow_node.progress += speed * delta
|
||||
if path_follow_node.progress_ratio >= 1.0 and not loop:
|
||||
active = false
|
||||
path_follow_node.set_process(false)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node2D) -> void:
|
||||
if activate_on_enter:
|
||||
start_wave()
|
||||
|
||||
|
||||
func start_wave() -> void:
|
||||
if path_follow_node:
|
||||
path_follow_node.set_process(true)
|
||||
active = true
|
@@ -1 +0,0 @@
|
||||
uid://tmahwsvpkrbv
|
@@ -1,39 +0,0 @@
|
||||
class_name ExitDoorComponent
|
||||
extends Node
|
||||
|
||||
@export var locked: bool = true
|
||||
@export var exit_area: Area2D
|
||||
@export var door_sprite: Sprite2D
|
||||
@export var opened_door_sfx: AudioStreamPlayer2D
|
||||
@export var opened_door_frame: int = 0
|
||||
signal exit_triggered
|
||||
@onready var gm: GM = $"/root/GameManager"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not exit_area:
|
||||
printerr("ExitDoorComponent: exit_area is not set.")
|
||||
return
|
||||
|
||||
exit_area.body_entered.connect(on_exit_area_body_entered)
|
||||
|
||||
|
||||
func unlock() -> void:
|
||||
locked = false
|
||||
if door_sprite:
|
||||
door_sprite.frame = opened_door_frame
|
||||
if opened_door_sfx:
|
||||
opened_door_sfx.play()
|
||||
|
||||
|
||||
func on_exit_area_body_entered(_body: Node2D) -> void:
|
||||
if locked:
|
||||
return
|
||||
|
||||
exit_triggered.emit()
|
||||
gm.unlock_level(gm.player_state["current_level"] + 1)
|
||||
call_deferred("go_to_next_level")
|
||||
|
||||
|
||||
func go_to_next_level() -> void:
|
||||
gm.on_level_complete()
|
@@ -1 +0,0 @@
|
||||
uid://bwamqffvpa452
|
@@ -1,64 +0,0 @@
|
||||
class_name ExplosiveComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node2D
|
||||
@export var damage: DamageComponent
|
||||
@export var area2d: Area2D
|
||||
@export var explosion_area2d: Area2D
|
||||
@export var explosion_effect: PackedScene
|
||||
@export var time_to_explode: float = 9.0
|
||||
signal on_explosion(body: Node2D)
|
||||
var timer: Timer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not damage:
|
||||
printerr("No damage component assigned!")
|
||||
return
|
||||
|
||||
if not explosion_area2d:
|
||||
printerr("No area2d assigned!")
|
||||
return
|
||||
|
||||
area2d.body_entered.connect(on_area2d_body_entered)
|
||||
area2d.area_entered.connect(on_area2d_area_entered)
|
||||
|
||||
prepare_timer()
|
||||
|
||||
|
||||
func prepare_timer() -> void:
|
||||
timer = Timer.new()
|
||||
timer.set_wait_time(time_to_explode)
|
||||
timer.set_one_shot(true)
|
||||
timer.autostart = true
|
||||
timer.timeout.connect(explode)
|
||||
add_child(timer)
|
||||
|
||||
|
||||
func explode() -> void:
|
||||
timer.stop()
|
||||
|
||||
if explosion_effect:
|
||||
var explosion_instance: GPUParticles2D = explosion_effect.instantiate()
|
||||
explosion_instance.global_position = root.global_position
|
||||
get_tree().current_scene.add_child(explosion_instance)
|
||||
explosion_instance.emitting = true
|
||||
|
||||
var bodies: Array = explosion_area2d.get_overlapping_bodies()
|
||||
for body in bodies:
|
||||
var health_component: HealthComponent = body.get_node_or_null("HealthComponent")
|
||||
|
||||
if damage and health_component:
|
||||
damage.deal_damage(health_component)
|
||||
|
||||
on_explosion.emit(body)
|
||||
|
||||
root.queue_free()
|
||||
|
||||
|
||||
func on_area2d_body_entered(_body: Node2D) -> void:
|
||||
explode()
|
||||
|
||||
|
||||
func on_area2d_area_entered(_area: Area2D) -> void:
|
||||
explode()
|
@@ -1 +0,0 @@
|
||||
uid://beg4dk7d5pvhp
|
@@ -1,27 +0,0 @@
|
||||
class_name FadeAwayComponent
|
||||
extends Node
|
||||
|
||||
@export var sprite2d: Sprite2D
|
||||
@export var fade_duration: float = 1.0
|
||||
@export var speed: float = 10.0
|
||||
@export var direction: Vector2 = Vector2.UP
|
||||
@export var root: Node2D
|
||||
@export var area2d: Area2D
|
||||
|
||||
|
||||
func _ready():
|
||||
root = get_parent()
|
||||
if area2d:
|
||||
area2d.body_entered.connect(on_area2d_body_entered)
|
||||
|
||||
|
||||
func fade_away() -> void:
|
||||
var fade_tween := create_tween().set_parallel(true)
|
||||
fade_tween.tween_property(sprite2d, "modulate:a", 0, fade_duration)
|
||||
fade_tween.tween_property(sprite2d, "position", sprite2d.position + (direction * speed), fade_duration)
|
||||
await (fade_tween.finished)
|
||||
root.queue_free()
|
||||
|
||||
|
||||
func on_area2d_body_entered(_body: Node2D) -> void:
|
||||
fade_away()
|
@@ -1 +0,0 @@
|
||||
uid://bg75hnr3q6grk
|
@@ -1,54 +0,0 @@
|
||||
class_name FireEffectComponent
|
||||
extends Node
|
||||
|
||||
@export var health_component: HealthComponent
|
||||
@export var status_effect_component: StatusEffectComponent
|
||||
@export var root: Node2D
|
||||
@export var fire_fx: GPUParticles2D
|
||||
|
||||
var data: StatusEffectDataResource = null
|
||||
var should_deal_damage: bool = false
|
||||
var time_elapsed: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not health_component:
|
||||
health_component = root.get_node("HealthComponent")
|
||||
if not status_effect_component:
|
||||
status_effect_component = root.get_node("StatusEffectComponent")
|
||||
|
||||
if not health_component:
|
||||
printerr("No HealthComponent assigned!")
|
||||
return
|
||||
if not status_effect_component:
|
||||
printerr("No StatusEffectComponent assigned!")
|
||||
return
|
||||
|
||||
status_effect_component.effect_applied.connect(on_effect_applied)
|
||||
status_effect_component.effect_removed.connect(on_effect_removed)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not should_deal_damage or not health_component or not data:
|
||||
return
|
||||
|
||||
time_elapsed += delta
|
||||
if time_elapsed >= 1.0:
|
||||
health_component.decrease_health(data.damage_per_second)
|
||||
time_elapsed = 0.0
|
||||
|
||||
|
||||
func on_effect_applied(effect_data: StatusEffectDataResource) -> void:
|
||||
if effect_data.effect_type == StatusEffectComponent.EffectType.FIRE:
|
||||
data = effect_data
|
||||
should_deal_damage = true
|
||||
if fire_fx:
|
||||
fire_fx.emitting = true
|
||||
|
||||
|
||||
func on_effect_removed(effect_type: StatusEffectComponent.EffectType) -> void:
|
||||
if effect_type == StatusEffectComponent.EffectType.FIRE:
|
||||
data = null
|
||||
should_deal_damage = false
|
||||
if fire_fx:
|
||||
fire_fx.emitting = false
|
@@ -1 +0,0 @@
|
||||
uid://qi2irprbqru2
|
@@ -1,55 +0,0 @@
|
||||
class_name FlashingComponent
|
||||
extends Node
|
||||
|
||||
@export var sprite: Node2D
|
||||
@export var flash_duration: float = 0.5
|
||||
@export var flash_time: float = 0.1
|
||||
@export var use_modulate: bool = true
|
||||
@export var health_component: HealthComponent
|
||||
|
||||
var tween: Tween
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if health_component:
|
||||
health_component.on_health_change.connect(on_health_change)
|
||||
health_component.on_death.connect(on_death)
|
||||
|
||||
if not sprite:
|
||||
printerr("No sprite assigned!")
|
||||
return
|
||||
|
||||
|
||||
func start_flashing() -> void:
|
||||
if not sprite:
|
||||
return
|
||||
|
||||
if tween:
|
||||
tween.kill()
|
||||
|
||||
tween = create_tween()
|
||||
tween.set_parallel(false)
|
||||
|
||||
var flashes: int = int(flash_duration / flash_time)
|
||||
for i in range(flashes):
|
||||
var opacity: float = 0.3 if i % 2 == 0 else 1.0
|
||||
tween.tween_property(sprite, "modulate:a" if use_modulate else "visible", opacity if use_modulate else float(i % 2 == 0), flash_time)
|
||||
|
||||
tween.tween_callback(stop_flashing)
|
||||
|
||||
|
||||
func stop_flashing() -> void:
|
||||
if use_modulate:
|
||||
sprite.modulate.a = 1.0
|
||||
else:
|
||||
sprite.visible = true
|
||||
|
||||
|
||||
func on_health_change(delta: float, _total_health: float) -> void:
|
||||
if delta < 0:
|
||||
start_flashing()
|
||||
|
||||
|
||||
func on_death() -> void:
|
||||
stop_flashing()
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://dqmbvuutd5c3c
|
@@ -1,27 +0,0 @@
|
||||
class_name FlipPlayerComponent
|
||||
extends Node2D
|
||||
|
||||
@export var eye_left: Sprite2D
|
||||
@export var eye_right: Sprite2D
|
||||
@export var platform_movement: PlatformMovement
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not platform_movement:
|
||||
return
|
||||
|
||||
var velocity := platform_movement.last_direction
|
||||
if velocity.x < 0:
|
||||
eye_left.frame = 1
|
||||
eye_right.frame = 1
|
||||
eye_left.flip_h = true
|
||||
eye_right.flip_h = true
|
||||
elif velocity.x > 0:
|
||||
eye_left.frame = 1
|
||||
eye_right.frame = 1
|
||||
eye_left.flip_h = false
|
||||
eye_right.flip_h = false
|
||||
else:
|
||||
eye_left.frame = 0
|
||||
eye_right.frame = 0
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://oxeqvxkgj87j
|
@@ -1,30 +0,0 @@
|
||||
class_name GravityMotionComponent
|
||||
extends Node2D
|
||||
|
||||
@export var character_body: CharacterBody2D
|
||||
@export var launch_component: LaunchComponent
|
||||
@export var gravity: Vector2 = Vector2(0, 980.0)
|
||||
@export var target_direction: Vector2 = Vector2(1.0, -1.0)
|
||||
|
||||
var velocity: Vector2 = Vector2.ZERO
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not launch_component:
|
||||
return
|
||||
var direction := target_direction if launch_component.initial_direction.x > 0 else Vector2(-target_direction.x, target_direction.y)
|
||||
direction = direction.normalized()
|
||||
velocity = direction * launch_component.speed
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not character_body:
|
||||
return
|
||||
|
||||
velocity += gravity * delta
|
||||
character_body.velocity = velocity
|
||||
|
||||
character_body.move_and_slide()
|
||||
|
||||
if velocity.length_squared() > 0.01:
|
||||
character_body.rotation = velocity.angle()
|
@@ -1 +0,0 @@
|
||||
uid://c2gbumw4x4t1v
|
@@ -1,40 +0,0 @@
|
||||
class_name HealComponent
|
||||
extends Node
|
||||
|
||||
@export var heal_fx: GPUParticles2D
|
||||
@export var collectable: CollectableComponent
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not collectable:
|
||||
printerr("HealComponent: No CollectableComponent assigned.")
|
||||
return
|
||||
|
||||
collectable.collected.connect(on_collected)
|
||||
|
||||
|
||||
func on_collected(amount: float, type: CollectableResource.CollectableType, _body: Node2D) -> void:
|
||||
if type != CollectableResource.CollectableType.HEALTH:
|
||||
return
|
||||
|
||||
if not collectable:
|
||||
printerr("HealComponent: No CollectableComponent assigned.")
|
||||
return
|
||||
|
||||
var health_component := _body.get_node_or_null("HealthComponent") as HealthComponent
|
||||
if not health_component or not health_component is HealthComponent:
|
||||
printerr("HealComponent: No HealthComponent found on collected body.")
|
||||
return
|
||||
health_component.increase_health(amount)
|
||||
if heal_fx:
|
||||
play_heal_fx()
|
||||
else:
|
||||
owner.queue_free()
|
||||
|
||||
|
||||
func play_heal_fx() -> void:
|
||||
if not heal_fx:
|
||||
return
|
||||
|
||||
heal_fx.restart()
|
||||
heal_fx.emitting = true
|
@@ -1 +0,0 @@
|
||||
uid://cegdd1sravi5m
|
@@ -1,48 +0,0 @@
|
||||
class_name HealthComponent
|
||||
extends Node
|
||||
|
||||
@export var health: float = 1.0
|
||||
@export var max_health: float = 1.0
|
||||
@export var hurt_fx: AudioStreamPlayer2D
|
||||
@export var heal_fx: AudioStreamPlayer2D
|
||||
signal on_health_change(delta: float, total_health: float)
|
||||
signal on_death
|
||||
|
||||
|
||||
|
||||
func _get_delta(new_value: float) -> float:
|
||||
return new_value - health
|
||||
|
||||
|
||||
func set_health(new_value: float):
|
||||
_apply_health_change(new_value)
|
||||
|
||||
|
||||
func decrease_health(value: float):
|
||||
_apply_health_change(health - value)
|
||||
|
||||
|
||||
func increase_health(value: float):
|
||||
_apply_health_change(health + value)
|
||||
|
||||
|
||||
func _apply_health_change(new_health: float, play_fx: bool = true) -> void:
|
||||
new_health = clamp(new_health, 0.0, max_health)
|
||||
var delta := new_health - health
|
||||
|
||||
if delta == 0.0:
|
||||
return # No change
|
||||
|
||||
if play_fx:
|
||||
if delta > 0 and heal_fx:
|
||||
heal_fx.play()
|
||||
elif delta < 0 and hurt_fx:
|
||||
hurt_fx.play()
|
||||
await hurt_fx.finished
|
||||
|
||||
health = new_health
|
||||
|
||||
if health <= 0:
|
||||
on_death.emit()
|
||||
else:
|
||||
on_health_change.emit(delta, health)
|
@@ -1 +0,0 @@
|
||||
uid://btfsq0bvtrx3t
|
@@ -1,57 +0,0 @@
|
||||
class_name HitComponent
|
||||
extends Node
|
||||
|
||||
@export var sprite: Sprite2D
|
||||
@export var health_component: HealthComponent
|
||||
@export var hit_duration: float = 0.1
|
||||
@export var hit_fx: GPUParticles2D
|
||||
@export var flash_mode: bool = true
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if health_component:
|
||||
health_component.on_health_change.connect(on_health_change)
|
||||
health_component.on_death.connect(on_death)
|
||||
|
||||
if not sprite:
|
||||
printerr("No sprite assigned!")
|
||||
return
|
||||
|
||||
if sprite.material and flash_mode:
|
||||
sprite.material = sprite.material.duplicate()
|
||||
|
||||
|
||||
func activate() -> void:
|
||||
if not flash_mode:
|
||||
return
|
||||
sprite.material.set_shader_parameter("enabled", true)
|
||||
|
||||
|
||||
func deactivate() -> void:
|
||||
if not flash_mode:
|
||||
return
|
||||
sprite.material.set_shader_parameter("enabled", false)
|
||||
|
||||
|
||||
func on_health_change(delta: float, total_health: float) -> void:
|
||||
if delta < 0:
|
||||
activate()
|
||||
await get_tree().create_timer(hit_duration).timeout
|
||||
deactivate()
|
||||
|
||||
if total_health > 0 and delta < 0:
|
||||
handle_hit_fx()
|
||||
|
||||
|
||||
func on_death() -> void:
|
||||
activate()
|
||||
await get_tree().create_timer(hit_duration).timeout
|
||||
deactivate()
|
||||
|
||||
|
||||
func handle_hit_fx() -> void:
|
||||
if not hit_fx:
|
||||
return
|
||||
|
||||
hit_fx.restart()
|
||||
hit_fx.emitting = true
|
@@ -1 +0,0 @@
|
||||
uid://ceq8n7yw7qxpi
|
@@ -1,71 +0,0 @@
|
||||
class_name HomingMissileMotion
|
||||
extends Node
|
||||
|
||||
@export var launch_component: LaunchComponent
|
||||
@export var root: Node2D
|
||||
@export var max_speed: float = 16.0
|
||||
@export var acceleration: float = 8.0
|
||||
@export var detection_area: Area2D
|
||||
@export var max_turn_rate: float = 180.0
|
||||
@export var wobble_strength := 5.0 # degrees
|
||||
@export var drag := 0.98
|
||||
@export var steering_lerp := 0.05 # low = sluggish
|
||||
|
||||
var steering_direction: Vector2 = velocity.normalized()
|
||||
var target: Node2D = null
|
||||
var velocity: Vector2 = Vector2.ZERO
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not detection_area:
|
||||
printerr("No detection area assigned!")
|
||||
return
|
||||
|
||||
if not launch_component:
|
||||
printerr("No launch component assigned!")
|
||||
return
|
||||
|
||||
detection_area.body_entered.connect(on_detection_area_body_entered)
|
||||
velocity = launch_component.get_initial_velocity()
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not launch_component or not root:
|
||||
return
|
||||
|
||||
if not target:
|
||||
root.position += velocity * delta
|
||||
return
|
||||
|
||||
var to_target := (target.global_position - root.global_position).normalized()
|
||||
steering_direction = steering_direction.lerp(to_target, steering_lerp)
|
||||
|
||||
var angle_to_target := velocity.angle_to(steering_direction)
|
||||
var max_angle := deg_to_rad(max_turn_rate) * delta
|
||||
var clamped_angle = clamp(angle_to_target, - max_angle, max_angle)
|
||||
|
||||
var wobble := deg_to_rad(randf_range(-wobble_strength, wobble_strength))
|
||||
clamped_angle += wobble
|
||||
|
||||
velocity = velocity.rotated(clamped_angle)
|
||||
|
||||
velocity *= drag
|
||||
|
||||
var desired_speed = min(max_speed, velocity.length() + acceleration * delta)
|
||||
velocity = velocity.normalized() * desired_speed
|
||||
|
||||
root.position += velocity * delta
|
||||
root.rotation = velocity.angle()
|
||||
|
||||
|
||||
func on_detection_area_body_entered(body: Node) -> void:
|
||||
if target != null:
|
||||
return
|
||||
|
||||
if body == null:
|
||||
return
|
||||
|
||||
target = body
|
||||
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://be8jhvb8t3kif
|
@@ -1,62 +0,0 @@
|
||||
class_name IceEffectComponent
|
||||
extends Node
|
||||
|
||||
@export var components_to_disable: Array = []
|
||||
@export var status_effect_component: StatusEffectComponent
|
||||
@export var ice_fx: Node2D
|
||||
|
||||
var data: StatusEffectDataResource = null
|
||||
var ice_effects_applied: int = 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not status_effect_component:
|
||||
status_effect_component = get_node("StatusEffectComponent")
|
||||
|
||||
if not status_effect_component:
|
||||
printerr("No StatusEffectComponent assigned!")
|
||||
return
|
||||
|
||||
status_effect_component.effect_applied.connect(on_effect_applied)
|
||||
status_effect_component.effect_removed.connect(on_effect_removed)
|
||||
|
||||
|
||||
func on_effect_applied(effect_data: StatusEffectDataResource) -> void:
|
||||
if effect_data.effect_type == StatusEffectComponent.EffectType.ICE:
|
||||
data = effect_data
|
||||
ice_effects_applied += 1
|
||||
apply_freeze()
|
||||
|
||||
|
||||
func on_effect_removed(effect_type: StatusEffectComponent.EffectType) -> void:
|
||||
if effect_type == StatusEffectComponent.EffectType.ICE:
|
||||
data = null
|
||||
ice_effects_applied -= 1
|
||||
remove_freeze()
|
||||
|
||||
|
||||
func apply_freeze() -> void:
|
||||
if ice_fx:
|
||||
ice_fx.visible = true
|
||||
|
||||
for component_path in components_to_disable:
|
||||
var component: Node = get_node_or_null(component_path)
|
||||
if not component or ice_effects_applied == 0:
|
||||
continue
|
||||
|
||||
component.process_mode = PROCESS_MODE_DISABLED
|
||||
|
||||
|
||||
func remove_freeze() -> void:
|
||||
if ice_effects_applied > 0:
|
||||
return
|
||||
|
||||
if ice_fx:
|
||||
ice_fx.visible = false
|
||||
|
||||
for component_path in components_to_disable:
|
||||
var component: Node = get_node_or_null(component_path)
|
||||
if not component:
|
||||
continue
|
||||
|
||||
component.process_mode = PROCESS_MODE_ALWAYS
|
@@ -1 +0,0 @@
|
||||
uid://dhj4qtwcqmqkj
|
@@ -1,30 +0,0 @@
|
||||
class_name InvulnerabilityComponent
|
||||
extends Node
|
||||
|
||||
@export var duration: float = 1.0
|
||||
@export var flashing_component: FlashingComponent
|
||||
|
||||
var invulnerable: bool = false
|
||||
|
||||
|
||||
func activate() -> void:
|
||||
if invulnerable:
|
||||
return
|
||||
|
||||
invulnerable = true
|
||||
if flashing_component:
|
||||
flashing_component.start_flashing()
|
||||
|
||||
var timer = get_tree().create_timer(duration)
|
||||
timer.timeout.connect(deactivate)
|
||||
|
||||
|
||||
func deactivate() -> void:
|
||||
invulnerable = false
|
||||
if flashing_component:
|
||||
flashing_component.stop_flashing()
|
||||
|
||||
|
||||
func is_invulnerable() -> bool:
|
||||
return invulnerable
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://ijrli0x8ij8v
|
@@ -1,42 +0,0 @@
|
||||
class_name JumpPadComponent
|
||||
extends Node
|
||||
|
||||
@export var jump_force: float = 10.0
|
||||
@export var area2d: Area2D
|
||||
@export var sprite2d: Sprite2D
|
||||
@export var start_animation_index: int = 0
|
||||
@export var animation_duration: float = 0.5
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not area2d:
|
||||
printerr("JumpPadComponent: area2d is not set.")
|
||||
return
|
||||
|
||||
if not sprite2d:
|
||||
printerr("JumpPadComponent: sprite2d is not set.")
|
||||
return
|
||||
|
||||
area2d.body_entered.connect(_on_body_entered)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node2D) -> void:
|
||||
var can_be_launched: CanBeLaunchedComponent = body.get_node_or_null("CanBeLaunchedComponent")
|
||||
if not can_be_launched:
|
||||
return
|
||||
|
||||
if body is PlayerController and body.current_movement is PlatformMovement:
|
||||
handle_launchpad_animation()
|
||||
body.velocity.y = -jump_force
|
||||
if body.current_movement.jump_sfx:
|
||||
body.current_movement.jump_sfx.play()
|
||||
|
||||
|
||||
func handle_launchpad_animation() -> void:
|
||||
if not sprite2d:
|
||||
return
|
||||
|
||||
var timer := get_tree().create_timer(animation_duration)
|
||||
sprite2d.frame = start_animation_index + 1
|
||||
await timer.timeout
|
||||
sprite2d.frame = start_animation_index
|
@@ -1 +0,0 @@
|
||||
uid://dwok2qx4wpkey
|
@@ -1,24 +0,0 @@
|
||||
class_name KillPlayerOutOfScreen
|
||||
extends Node
|
||||
|
||||
@export var screen_notifier: VisibleOnScreenNotifier2D
|
||||
@export var health_component: HealthComponent
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not screen_notifier:
|
||||
printerr("KillPlayerOutOfScreen: screen_notifier is not set.")
|
||||
return
|
||||
|
||||
if not health_component:
|
||||
printerr("KillPlayerOutOfScreen: health_component is not set.")
|
||||
return
|
||||
|
||||
screen_notifier.screen_exited.connect(out_of_screen)
|
||||
|
||||
|
||||
func out_of_screen() -> void:
|
||||
if not health_component:
|
||||
return
|
||||
|
||||
health_component.decrease_health(6000)
|
@@ -1 +0,0 @@
|
||||
uid://cfeoalic0mu2j
|
@@ -1,32 +0,0 @@
|
||||
class_name KnockbackComponent
|
||||
extends Node
|
||||
|
||||
@export var character_body: CharacterBody2D
|
||||
@export var knockback_force: float = 25.0
|
||||
|
||||
var knockback_mode: bool = false
|
||||
var knockback_frames: int = 0
|
||||
|
||||
func apply_knockback(force: float, delta: float) -> void:
|
||||
var velocity = character_body.velocity.normalized()
|
||||
var knockback_dir = Vector2(sign(velocity.x) * 1.0, 0.4)
|
||||
var knockback_vector = -knockback_dir * force * delta
|
||||
character_body.velocity += knockback_vector
|
||||
|
||||
func _on_health_component_on_health_change(delta: float, total_health: float) -> void:
|
||||
if total_health <= 0.0 and delta < 0.0:
|
||||
return
|
||||
knockback_mode = true
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if knockback_mode:
|
||||
knockback_frames += 1
|
||||
if knockback_frames > 1:
|
||||
knockback_mode = false
|
||||
knockback_frames = 0
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if knockback_mode:
|
||||
apply_knockback(knockback_force, delta)
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://nogmyshjrv57
|
@@ -1,17 +0,0 @@
|
||||
class_name LaunchComponent
|
||||
extends Node2D
|
||||
|
||||
@export var root: Node2D
|
||||
@export var initial_direction: Vector2 = Vector2.RIGHT
|
||||
@export var speed: float = 16.0
|
||||
@export var spawn_position: Vector2 = Vector2.ZERO
|
||||
@export var spawn_rotation: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
root.global_position = spawn_position
|
||||
root.global_rotation = spawn_rotation
|
||||
|
||||
|
||||
func get_initial_velocity() -> Vector2:
|
||||
return initial_direction.normalized() * speed
|
@@ -1 +0,0 @@
|
||||
uid://873un8agkyja
|
@@ -1,49 +0,0 @@
|
||||
class_name LeverComponent
|
||||
extends Node
|
||||
|
||||
@export var area2d: Area2D
|
||||
@export var sprite2d: Sprite2D
|
||||
@export var start_animation_index: int = 0
|
||||
@export var animation_duration: float = 0.5
|
||||
@export var sfx: AudioStreamPlayer2D
|
||||
signal activated
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not area2d:
|
||||
printerr("LeverComponent: area2d is not set.")
|
||||
return
|
||||
|
||||
if not sprite2d:
|
||||
printerr("LeverComponent: sprite2d is not set.")
|
||||
return
|
||||
|
||||
area2d.body_entered.connect(_on_body_entered)
|
||||
area2d.area_entered.connect(_on_area_entered)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node2D) -> void:
|
||||
var trigger_lever: TriggerLeverComponent = body.get_node_or_null("TriggerLeverComponent")
|
||||
if not trigger_lever:
|
||||
return
|
||||
|
||||
activate()
|
||||
|
||||
|
||||
|
||||
func _on_area_entered(area: Area2D) -> void:
|
||||
var trigger_lever: TriggerLeverComponent = area.get_node_or_null("TriggerLeverComponent")
|
||||
if not trigger_lever:
|
||||
return
|
||||
|
||||
activate()
|
||||
|
||||
|
||||
func activate() -> void:
|
||||
activated.emit()
|
||||
if sfx:
|
||||
sfx.play()
|
||||
sprite2d.frame = start_animation_index + 1
|
||||
var timer := get_tree().create_timer(animation_duration)
|
||||
await timer.timeout
|
||||
sprite2d.frame = start_animation_index
|
@@ -1 +0,0 @@
|
||||
uid://hyuwsp1b336a
|
@@ -1,22 +0,0 @@
|
||||
class_name LifetimeComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node
|
||||
@export var life_time: float = 5.0
|
||||
@export var timer: Timer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not root:
|
||||
printerr("Root node not set.")
|
||||
return
|
||||
|
||||
if not timer:
|
||||
printerr("Timer node not set.")
|
||||
return
|
||||
|
||||
timer.timeout.connect(on_timer_timeout)
|
||||
|
||||
|
||||
func on_timer_timeout() -> void:
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://bvsgg8lu0a8m6
|
@@ -1,63 +0,0 @@
|
||||
class_name MagneticSkillComponent
|
||||
extends Node
|
||||
|
||||
@export var magnetic_area: Area2D
|
||||
@export var root: Node2D
|
||||
@export var magnetic_move_duration: float = 1.25
|
||||
|
||||
var collectables_to_pickup: Array = []
|
||||
|
||||
|
||||
func has_component_in_children(node: Node, component_name: String) -> bool:
|
||||
if not node:
|
||||
return false
|
||||
|
||||
if node.has_node(component_name):
|
||||
return true
|
||||
|
||||
for child in node.get_children():
|
||||
if has_component_in_children(child, component_name):
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not magnetic_area:
|
||||
printerr("No magnetic_area assigned!")
|
||||
return
|
||||
|
||||
magnetic_area.body_entered.connect(on_magnetic_area_body_entered)
|
||||
magnetic_area.area_entered.connect(on_magnetic_area_area_entered)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
for collectable in collectables_to_pickup:
|
||||
if not is_instance_valid(collectable):
|
||||
collectables_to_pickup.erase(collectable)
|
||||
continue
|
||||
|
||||
move_collectable_to_root(collectable)
|
||||
|
||||
|
||||
func on_magnetic_area_body_entered(_body: Node2D) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func on_magnetic_area_area_entered(area: Area2D) -> void:
|
||||
if not has_component_in_children(area, "Collectable"):
|
||||
return
|
||||
|
||||
if not collectables_to_pickup.has(area):
|
||||
collectables_to_pickup.append(area)
|
||||
|
||||
|
||||
func move_collectable_to_root(collectable: Node2D) -> void:
|
||||
if not is_instance_valid(collectable):
|
||||
return
|
||||
|
||||
var direction: Vector2 = (root.global_position - collectable.global_position).normalized()
|
||||
var speed: float = direction.length() / magnetic_move_duration
|
||||
|
||||
collectable.global_position += direction.normalized() * speed
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://ce8w71vgv37pt
|
@@ -1,21 +0,0 @@
|
||||
class_name OutOfScreenComponent
|
||||
extends Node
|
||||
|
||||
@export var visibility_notifier: VisibleOnScreenNotifier2D
|
||||
@export var root: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not visibility_notifier:
|
||||
printerr("Visibility notifier not set.")
|
||||
return
|
||||
|
||||
visibility_notifier.screen_exited.connect(_on_screen_exited)
|
||||
|
||||
|
||||
func _on_screen_exited() -> void:
|
||||
if not root:
|
||||
printerr("Root node not set.")
|
||||
return
|
||||
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://1tnr46o1ib4u
|
@@ -1,60 +0,0 @@
|
||||
class_name PeriodicShootingComponent
|
||||
extends Node
|
||||
|
||||
@export var bullet_scene: PackedScene = preload("res://objects/entities/bullet.tscn")
|
||||
@export var shoot_interval: float = 1.0
|
||||
@export var shoot_direction: Vector2 = Vector2.RIGHT
|
||||
@export var side_to_side_movement: SideToSideMovement
|
||||
@export var root: Node2D
|
||||
@export var bullet_spawn_right: Node2D
|
||||
@export var bullet_spawn_left: Node2D
|
||||
@export var shooting_interval_variation: float = 0.0
|
||||
|
||||
var timer: Timer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
root = get_parent()
|
||||
setup_timer()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if side_to_side_movement:
|
||||
shoot_direction = side_to_side_movement.direction if side_to_side_movement.direction != Vector2.ZERO else Vector2.RIGHT
|
||||
|
||||
|
||||
func shoot() -> void:
|
||||
if shoot_direction == Vector2.ZERO:
|
||||
return
|
||||
|
||||
var bullet_instance: Node2D = bullet_scene.instantiate()
|
||||
var launch_component: LaunchComponent = bullet_instance.get_node_or_null("LaunchComponent")
|
||||
var spawn_position: Vector2 = bullet_spawn_right.global_position if shoot_direction == Vector2.RIGHT else bullet_spawn_left.global_position
|
||||
if launch_component:
|
||||
launch_component.initial_direction = shoot_direction
|
||||
launch_component.spawn_position = spawn_position
|
||||
launch_component.spawn_rotation = root.rotation
|
||||
|
||||
bullet_instance.position = spawn_position
|
||||
get_tree().current_scene.add_child(bullet_instance)
|
||||
|
||||
|
||||
func on_timer_timeout() -> void:
|
||||
shoot()
|
||||
timer.start()
|
||||
|
||||
|
||||
|
||||
func get_shoot_interval() -> float:
|
||||
if shooting_interval_variation > 0.0:
|
||||
return shoot_interval + randf_range(-shooting_interval_variation, shooting_interval_variation)
|
||||
return shoot_interval
|
||||
|
||||
|
||||
func setup_timer() -> void:
|
||||
timer = Timer.new()
|
||||
timer.wait_time = get_shoot_interval()
|
||||
timer.one_shot = false
|
||||
timer.autostart = true
|
||||
timer.timeout.connect(on_timer_timeout)
|
||||
add_child(timer)
|
@@ -1 +0,0 @@
|
||||
uid://b37gqnycj6rtk
|
@@ -1,118 +0,0 @@
|
||||
class_name PlatformMovement
|
||||
extends PlayerMovement
|
||||
|
||||
@export var speed: float = 300.0
|
||||
@export var jump_height: float = 100
|
||||
@export var jump_time_to_peak: float = 0.5
|
||||
@export var jump_time_to_descent: float = 0.4
|
||||
@export var coyote_frames: int = 6
|
||||
@export var jump_sfx: AudioStreamPlayer2D
|
||||
@export var rotation_target: Node2D
|
||||
@export var body: CharacterBody2D
|
||||
|
||||
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
|
||||
var was_last_floor := false
|
||||
var coyote_mode := false
|
||||
var coyote_timer: Timer
|
||||
var last_direction := Vector2.RIGHT
|
||||
|
||||
@onready var jump_velocity: float = ((2.0 * jump_height) / jump_time_to_peak) * -1.0
|
||||
@onready var jump_gravity: float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
|
||||
@onready var fall_gravity: float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not body:
|
||||
return
|
||||
|
||||
coyote_timer = Timer.new()
|
||||
coyote_timer.one_shot = true
|
||||
coyote_timer.wait_time = coyote_frames / 60.0
|
||||
coyote_timer.timeout.connect(on_coyote_timer_timeout)
|
||||
add_child(coyote_timer)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not body or not enabled:
|
||||
return
|
||||
|
||||
if body.velocity.x > 0.0:
|
||||
rotation_target.rotation = deg_to_rad(-10)
|
||||
elif body.velocity.x < 0.0:
|
||||
rotation_target.rotation = deg_to_rad(10)
|
||||
else:
|
||||
rotation_target.rotation = 0
|
||||
|
||||
calculate_jump_vars()
|
||||
|
||||
|
||||
func _physics_process(delta) -> void:
|
||||
if not body or not enabled:
|
||||
return
|
||||
|
||||
if body.is_on_floor():
|
||||
was_last_floor = true
|
||||
coyote_mode = false # Reset coyote mode when back on the floor
|
||||
coyote_timer.stop() # Stop timer when grounded
|
||||
else:
|
||||
if was_last_floor: # Start coyote timer only once
|
||||
coyote_mode = true
|
||||
coyote_timer.start()
|
||||
was_last_floor = false
|
||||
|
||||
if not body.is_on_floor():
|
||||
body.velocity.y += calculate_gravity() * delta
|
||||
|
||||
if Input.is_action_pressed("jump") and (body.is_on_floor() or coyote_mode):
|
||||
jump()
|
||||
|
||||
if Input.is_action_just_pressed("down"):
|
||||
body.position.y += 1
|
||||
|
||||
var direction := Input.get_axis("left", "right")
|
||||
if direction != 0:
|
||||
last_direction = handle_direction(direction)
|
||||
|
||||
if direction:
|
||||
body.velocity.x = direction * speed
|
||||
else:
|
||||
body.velocity.x = move_toward(body.velocity.x, 0, speed)
|
||||
|
||||
previous_velocity = body.velocity
|
||||
body.move_and_slide()
|
||||
|
||||
|
||||
func jump() -> void:
|
||||
if not body:
|
||||
return
|
||||
|
||||
body.velocity.y = jump_velocity
|
||||
coyote_mode = false
|
||||
if jump_sfx:
|
||||
jump_sfx.play()
|
||||
|
||||
|
||||
func calculate_gravity() -> float:
|
||||
return jump_gravity if body.velocity.y < 0.0 else fall_gravity
|
||||
|
||||
|
||||
func on_coyote_timer_timeout() -> void:
|
||||
coyote_mode = false
|
||||
|
||||
|
||||
func handle_direction(input_dir: float) -> Vector2:
|
||||
if input_dir > 0:
|
||||
return Vector2.RIGHT
|
||||
elif input_dir < 0:
|
||||
return Vector2.LEFT
|
||||
return last_direction
|
||||
|
||||
|
||||
func on_ship_entered() -> void:
|
||||
rotation_target.rotation = 0
|
||||
|
||||
|
||||
func calculate_jump_vars() -> void:
|
||||
jump_velocity = ((2.0 * jump_height) / jump_time_to_peak) * -1.0
|
||||
jump_gravity = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
|
||||
fall_gravity = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
|
@@ -1 +0,0 @@
|
||||
uid://cflncpa377l8l
|
@@ -1,21 +0,0 @@
|
||||
class_name PlayerDeathComponent
|
||||
extends Node2D
|
||||
|
||||
@export var death_sfx: AudioStreamPlayer2D
|
||||
@export var death_effect: PackedScene
|
||||
|
||||
@onready var gm: GM = $"/root/GameManager"
|
||||
|
||||
|
||||
func _on_health_component_on_death() -> void:
|
||||
if death_sfx:
|
||||
death_sfx.play()
|
||||
if death_effect:
|
||||
var effect: Node2D = death_effect.instantiate()
|
||||
get_parent().add_child(effect)
|
||||
effect.global_position = global_position
|
||||
effect.scale = Vector2(1.5, 1.5)
|
||||
|
||||
gm.remove_lives(1)
|
||||
gm.reset_current_session_state()
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://dkpu3121y88oo
|
@@ -1,17 +0,0 @@
|
||||
class_name PlayerMovement
|
||||
extends Node
|
||||
|
||||
@export var type: String = ""
|
||||
|
||||
var enabled: bool = true
|
||||
var previous_velocity: Vector2 = Vector2.ZERO
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if not enabled:
|
||||
return
|
@@ -1 +0,0 @@
|
||||
uid://bqtc3sxew0sau
|
@@ -1,67 +0,0 @@
|
||||
class_name ProgressiveDamageComponent
|
||||
extends Node
|
||||
|
||||
@export var health_component: HealthComponent
|
||||
@export var sprite: Sprite2D
|
||||
@export var platform_movement: PlatformMovement
|
||||
@export var min_jump_height: float = 60.0
|
||||
@export var jump_reduction_percentage: float = 0.1 # this is a percentage of the jump height per hit
|
||||
|
||||
@onready var max_health: float = health_component.max_health
|
||||
|
||||
var og_jump_height: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not health_component:
|
||||
printerr("ProgressiveDamageComponent: health_component is not set.")
|
||||
return
|
||||
if not sprite:
|
||||
printerr("ProgressiveDamageComponent: sprite is not set.")
|
||||
return
|
||||
|
||||
health_component.on_health_change.connect(on_health_change)
|
||||
|
||||
if platform_movement:
|
||||
og_jump_height = platform_movement.jump_height
|
||||
|
||||
|
||||
func get_damage_frame() -> int:
|
||||
if not sprite or not health_component:
|
||||
return 0
|
||||
|
||||
var frames_count := sprite.get_hframes()
|
||||
if frames_count == 0:
|
||||
return 0
|
||||
|
||||
var current_health := health_component.health
|
||||
var health_ratio := current_health / max_health
|
||||
return int(frames_count * (1.0 - health_ratio))
|
||||
|
||||
|
||||
func get_jump_height() -> float:
|
||||
if not platform_movement:
|
||||
return 0.0
|
||||
|
||||
var jump_height := og_jump_height
|
||||
if jump_height <= 0:
|
||||
return 0.0
|
||||
|
||||
var damage_frame := get_damage_frame()
|
||||
if damage_frame < 0 or damage_frame >= sprite.get_hframes():
|
||||
return jump_height
|
||||
|
||||
var reduction := jump_reduction_percentage * jump_height
|
||||
var calculated_jump_height := jump_height - (damage_frame * reduction)
|
||||
return max(calculated_jump_height, min_jump_height)
|
||||
|
||||
|
||||
func on_health_change(_delta: float, _total_health: float) -> void:
|
||||
var frame := get_damage_frame()
|
||||
if frame < 0 or frame >= sprite.get_hframes():
|
||||
return
|
||||
|
||||
sprite.frame = frame
|
||||
if platform_movement:
|
||||
platform_movement.jump_height = get_jump_height()
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://d32kd83lf86iy
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user