Add new UID files and update scene configurations for dialogue components; refactor skill management and input handling

This commit is contained in:
2025-08-26 23:05:30 +02:00
parent 6e71c321f7
commit 6c733d3159
339 changed files with 18075 additions and 3491 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -2,6 +2,7 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components;
[GlobalClass]
public partial class ProjectileComponent : Node2D
{
[Export] public float Speed { get; set; } = 16f;

View File

@@ -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;

View File

@@ -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; }

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()

View File

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

View File

@@ -1,2 +0,0 @@
class_name CanBeLaunchedComponent
extends Node

View File

@@ -1 +0,0 @@
uid://6ffxsx3gknhr

View File

@@ -1,2 +0,0 @@
class_name CanPickUpComponent
extends Node

View File

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

View File

@@ -1,2 +0,0 @@
class_name CannotStompComponent
extends Node

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -1,8 +0,0 @@
class_name CleanUpComponent
extends Node
@export var root: Node
func clean_up() -> void:
root.queue_free()

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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)

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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)

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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)

View File

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

View File

@@ -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)

View File

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

View File

@@ -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

View File

@@ -1 +0,0 @@
uid://873un8agkyja

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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)

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

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

Some files were not shown because too many files have changed in this diff Show More