diff --git a/scenes/game_elements/props/character_sight/character_sight.gd b/scenes/game_elements/props/character_sight/character_sight.gd index d2bf17474..9edbdb2f6 100644 --- a/scenes/game_elements/props/character_sight/character_sight.gd +++ b/scenes/game_elements/props/character_sight/character_sight.gd @@ -22,7 +22,16 @@ signal interact_area_changed var is_looking_from_right: bool = false ## The area that the character is currently observing. -var interact_area: InteractArea +var interact_area: InteractArea: + set = _set_interact_area + + +func _set_interact_area(new_interact_area: InteractArea) -> void: + if interact_area: + interact_area.remove_observer(self) + interact_area = new_interact_area + if new_interact_area: + interact_area.add_observer(self) func _ready() -> void: diff --git a/scenes/game_elements/props/interact_area/interact_area.gd b/scenes/game_elements/props/interact_area/interact_area.gd index 0144f3a1a..1b595d054 100644 --- a/scenes/game_elements/props/interact_area/interact_area.gd +++ b/scenes/game_elements/props/interact_area/interact_area.gd @@ -17,6 +17,9 @@ extends Area2D signal interaction_started(player: Player, from_right: bool) signal interaction_ended +## Emitted when characters start or stop seeing this area for interaction. +signal observers_changed + const EXAMPLE_INTERACTION_FONT = preload("uid://c3bb7lmvdqc5e") const EXAMPLE_INTERACTION_FONT_SIZE = 34 @@ -33,6 +36,13 @@ var interact_label_position: Vector2: set_collision_layer_value(Enums.CollisionLayers.INTERACTABLE, not disabled) @export var action: String = "Talk" +## Whether this area is being observed by one or more characters. +## That is, if a [CharacterSight] area is seeing this area for interaction. +var is_being_observed: bool: + get = _get_is_being_observed + +var _observers: Array[CharacterSight] = [] + func start_interaction(player: Player, from_right: bool) -> void: interaction_started.emit(player, from_right) @@ -46,6 +56,22 @@ func get_global_interact_label_position() -> Vector2: return to_global(interact_label_position) +## A [CharacterSight] calls this when it starts seeing this area. +func add_observer(character_sight: CharacterSight) -> void: + _observers.append(character_sight) + observers_changed.emit() + + +## A [CharacterSight] calls this when it stops seeing this area. +func remove_observer(character_sight: CharacterSight) -> void: + _observers.erase(character_sight) + observers_changed.emit() + + +func _get_is_being_observed() -> bool: + return bool(_observers.size()) + + func _ready() -> void: collision_layer = 0 collision_mask = 0 diff --git a/scenes/game_elements/props/powerup/components/default_powerup.dialogue b/scenes/game_elements/props/powerup/components/default_powerup.dialogue new file mode 100644 index 000000000..8dbaa4848 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/default_powerup.dialogue @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +~ start +[wave amp=25 freq=5]You got a new ability! [b]{{ability_name}}[/b][/wave] +=> END diff --git a/scenes/game_elements/props/powerup/components/default_powerup.dialogue.import b/scenes/game_elements/props/powerup/components/default_powerup.dialogue.import new file mode 100644 index 000000000..100b7ee40 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/default_powerup.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://cj0i5jwlv8idi" +path="res://.godot/imported/default_powerup.dialogue-7b4f95958377611890379db90e1904ef.tres" + +[deps] + +source_file="res://scenes/game_elements/props/powerup/components/default_powerup.dialogue" +dest_files=["res://.godot/imported/default_powerup.dialogue-7b4f95958377611890379db90e1904ef.tres"] + +[params] + +defaults=true diff --git a/scenes/game_elements/props/powerup/components/powerup.aseprite b/scenes/game_elements/props/powerup/components/powerup.aseprite new file mode 100644 index 000000000..6ebc3d7d1 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup.aseprite @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85521554f47d1985edb2fa23d6e55702f4f909b28564985522f73ac2cb2cdf9b +size 955 diff --git a/scenes/game_elements/props/powerup/components/powerup.gd b/scenes/game_elements/props/powerup/components/powerup.gd new file mode 100644 index 000000000..85aa79952 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup.gd @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +@tool +extends Node2D +## A powerup that, when interacted, enables a player ability. +## +## By default it also displays a dialogue telling the player that an ability +## has been obtained. + +## Emitted when this powerup is collected. +signal collected + +## The player ability to enable. +@export var ability: Enums.PlayerAbilities = Enums.PlayerAbilities.ABILITY_A + +## Name for the ability to display if using the default dialogue. +@export var ability_name: String + +## Text to display in the label when the player gets close to interact with this powerup. +@export var interact_action: String: + set = _set_interact_action + +## Asset of this powerup. +@export var sprite_frames: SpriteFrames: + set = _set_sprite_frames + +## The powerup shines with this color through a shader. +@export var highlight_color: Color = Color.WHITE: + set = _set_highlight_color + +## Dialogue to display when collecting the powerup. +@export var dialogue: DialogueResource = preload("uid://cj0i5jwlv8idi") + +var _tween: Tween + +@onready var interact_area: InteractArea = %InteractArea +@onready var highlight_effect: Sprite2D = %HighlightEffect +@onready var sprite: AnimatedSprite2D = %Sprite +@onready var interact_collision: CollisionShape2D = %InteractCollision +@onready var ground_collision: CollisionShape2D = %GroundCollision + + +func _set_interact_action(new_interact_action: String) -> void: + interact_action = new_interact_action + if is_node_ready(): + interact_area.action = interact_action + + +func _set_sprite_frames(new_sprite_frames: SpriteFrames) -> void: + sprite_frames = new_sprite_frames + if is_node_ready(): + sprite.sprite_frames = sprite_frames + sprite.play("default") + + +func _set_highlight_color(new_highlight_color: Color) -> void: + highlight_color = new_highlight_color + if is_node_ready(): + highlight_effect.modulate = highlight_color + + +func _ready() -> void: + _set_interact_action(interact_action) + _set_sprite_frames(sprite_frames) + _set_highlight_color(highlight_color) + if Engine.is_editor_hint(): + return + GameState.abilities_changed.connect(_on_abilities_changed) + _on_abilities_changed() + + +func _notification(what: int) -> void: + match what: + NOTIFICATION_EDITOR_PRE_SAVE: + # Since this is a tool script that plays the animations in the + # editor, reset the frame progress before saving the scene. + sprite.frame_progress = 0 + + +func _on_abilities_changed() -> void: + var has_ability := GameState.has_ability(ability) + ground_collision.disabled = has_ability + interact_collision.disabled = has_ability + highlight_effect.visible = not has_ability + var alpha: float = 0.5 if has_ability else 1.0 + sprite.modulate = Color(Color.WHITE, alpha) + _set_highlight_color(highlight_color) + var highlight_material := highlight_effect.material as ShaderMaterial + if highlight_material: + highlight_material.set_shader_parameter(&"width", 0.4) + + +func _on_interact_area_interaction_started( + _player: Player, _from_right: bool, source: InteractArea +) -> void: + if _tween: + _tween.kill() + _tween = create_tween() + _tween.tween_property(highlight_effect, "modulate", Color.WHITE, 0.5) + _tween.tween_property(highlight_effect, "material:shader_parameter/width", 1.0, 1.0) + await _tween.finished + if dialogue: + DialogueManager.show_dialogue_balloon(dialogue, "", [self]) + await DialogueManager.dialogue_ended + source.end_interaction() + GameState.set_ability(ability, true) + collected.emit() + + +func _on_interact_area_observers_changed() -> void: + if _tween: + _tween.kill() + _tween = create_tween() + var width: float = 0.8 if interact_area.is_being_observed else 0.4 + _tween.tween_property(highlight_effect, "material:shader_parameter/width", width, 1.0) diff --git a/scenes/game_elements/props/powerup/components/powerup.gd.uid b/scenes/game_elements/props/powerup/components/powerup.gd.uid new file mode 100644 index 000000000..9f7f8b35d --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup.gd.uid @@ -0,0 +1 @@ +uid://bhmae3daygmqh diff --git a/scenes/game_elements/props/powerup/components/powerup.png b/scenes/game_elements/props/powerup/components/powerup.png new file mode 100644 index 000000000..9a25def33 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5646b743efe7b14a3ff63d54302b17d29e958d917c4bfa05924dc7b2b2784617 +size 1022 diff --git a/scenes/game_elements/props/powerup/components/powerup.png.import b/scenes/game_elements/props/powerup/components/powerup.png.import new file mode 100644 index 000000000..d886e85b7 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr1cdu5ien6jq" +path="res://.godot/imported/powerup.png-a4ee45e73209b83508bc25ff7865937f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scenes/game_elements/props/powerup/components/powerup.png" +dest_files=["res://.godot/imported/powerup.png-a4ee45e73209b83508bc25ff7865937f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/game_elements/props/powerup/components/powerup_highlight.png b/scenes/game_elements/props/powerup/components/powerup_highlight.png new file mode 100644 index 000000000..c2849789b --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup_highlight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4163dde57282bfac63c54e2b59797735028d6289aae4135120c8695a2851b1b +size 6231 diff --git a/scenes/game_elements/props/powerup/components/powerup_highlight.png.import b/scenes/game_elements/props/powerup/components/powerup_highlight.png.import new file mode 100644 index 000000000..352ba026c --- /dev/null +++ b/scenes/game_elements/props/powerup/components/powerup_highlight.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbo1nd3usbxnr" +path="res://.godot/imported/powerup_highlight.png-deef463ba3599bc39e64667d2828727c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scenes/game_elements/props/powerup/components/powerup_highlight.png" +dest_files=["res://.godot/imported/powerup_highlight.png-deef463ba3599bc39e64667d2828727c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/game_elements/props/powerup/components/ripple_effect.gdshader b/scenes/game_elements/props/powerup/components/ripple_effect.gdshader new file mode 100644 index 000000000..0a9b274a7 --- /dev/null +++ b/scenes/game_elements/props/powerup/components/ripple_effect.gdshader @@ -0,0 +1,40 @@ +/** + * Ripple effect + * + * This shader expects the CanvasItem to be a gradient asset that goes from white to transparent. + * It adjusts the alpha to create a ripple effect of a certain width. + * + * SPDX-FileCopyrightText: The Threadbare Authors + * SPDX-License-Identifier: MPL-2.0 + */ +shader_type canvas_item; +render_mode blend_add; + +/** + * How smooth or sharp are the ripples. + */ +uniform float ease: hint_range(0.0, 2.0) = 2.0; + +/** + * This is perceived as the amount of ripples. + */ +uniform float modulo_width: hint_range(0.0, 1.0) = 0.2; + +/** + * The ripple width. + */ +uniform float width: hint_range(0.0, 1.0) = 0.4; + +/** + * The ripple speed. + */ +uniform float speed: hint_range(0.0, 2.0) = 0.2; + +void fragment() { + float sdf_alpha = mod(TIME * speed + COLOR.a, modulo_width); + float front_ease = smoothstep(0, ease, sdf_alpha); + float easeAmount = front_ease * modulo_width; + float brightness = COLOR.a - sdf_alpha + easeAmount; + + COLOR.a = smoothstep(1.0 - width, width+(1.0 - width), brightness); +} diff --git a/scenes/game_elements/props/powerup/components/ripple_effect.gdshader.uid b/scenes/game_elements/props/powerup/components/ripple_effect.gdshader.uid new file mode 100644 index 000000000..ca946bacf --- /dev/null +++ b/scenes/game_elements/props/powerup/components/ripple_effect.gdshader.uid @@ -0,0 +1 @@ +uid://v56tvdvw22lj diff --git a/scenes/game_elements/props/powerup/powerup.tscn b/scenes/game_elements/props/powerup/powerup.tscn new file mode 100644 index 000000000..35205f63b --- /dev/null +++ b/scenes/game_elements/props/powerup/powerup.tscn @@ -0,0 +1,77 @@ +[gd_scene format=3 uid="uid://dmevaymmt6wco"] + +[ext_resource type="Script" uid="uid://bhmae3daygmqh" path="res://scenes/game_elements/props/powerup/components/powerup.gd" id="1_5vrqc"] +[ext_resource type="Texture2D" uid="uid://dr1cdu5ien6jq" path="res://scenes/game_elements/props/powerup/components/powerup.png" id="2_bq5to"] +[ext_resource type="Script" uid="uid://du8wfijr35r35" path="res://scenes/game_elements/props/interact_area/interact_area.gd" id="2_q7xhf"] +[ext_resource type="Texture2D" uid="uid://bbo1nd3usbxnr" path="res://scenes/game_elements/props/powerup/components/powerup_highlight.png" id="4_bq5to"] +[ext_resource type="Shader" uid="uid://v56tvdvw22lj" path="res://scenes/game_elements/props/powerup/components/ripple_effect.gdshader" id="4_vllb8"] + +[sub_resource type="SpriteFrames" id="SpriteFrames_bq5to"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": ExtResource("2_bq5to") +}], +"loop": true, +"name": &"default", +"speed": 10.0 +}] + +[sub_resource type="CircleShape2D" id="CircleShape2D_5vrqc"] +radius = 50.0 + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bq5to"] +radius = 16.0 +height = 68.0 + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_spw1e"] +shader = ExtResource("4_vllb8") +shader_parameter/ease = 2.0 +shader_parameter/modulo_width = 0.2 +shader_parameter/width = 0.4 +shader_parameter/speed = 0.2 + +[node name="Powerup" type="Node2D" unique_id=1795462717] +script = ExtResource("1_5vrqc") +ability_name = "Repel" +interact_action = "Collect Repel" +sprite_frames = SubResource("SpriteFrames_bq5to") + +[node name="InteractArea" type="Area2D" parent="." unique_id=1443651205] +unique_name_in_owner = true +collision_layer = 32 +collision_mask = 0 +script = ExtResource("2_q7xhf") +interact_label_position = Vector2(0, -142) +action = "Collect Repel" + +[node name="InteractCollision" type="CollisionShape2D" parent="InteractArea" unique_id=1651676696] +unique_name_in_owner = true +position = Vector2(0, -33) +shape = SubResource("CircleShape2D_5vrqc") +debug_color = Color(0.600391, 0.54335, 0, 0.42) + +[node name="StaticBody2D" type="StaticBody2D" parent="." unique_id=21266648] +collision_mask = 0 + +[node name="GroundCollision" type="CollisionShape2D" parent="StaticBody2D" unique_id=1403567890] +unique_name_in_owner = true +rotation = -1.5707964 +shape = SubResource("CapsuleShape2D_bq5to") +debug_color = Color(0.976711, 0, 0.409753, 0.42) + +[node name="HighlightEffect" type="Sprite2D" parent="." unique_id=1449487876] +unique_name_in_owner = true +visible = false +material = SubResource("ShaderMaterial_spw1e") +position = Vector2(0, -34) +texture = ExtResource("4_bq5to") + +[node name="Sprite" type="AnimatedSprite2D" parent="." unique_id=682377422] +unique_name_in_owner = true +position = Vector2(0, -34) +sprite_frames = SubResource("SpriteFrames_bq5to") +autoplay = "default" + +[connection signal="interaction_started" from="InteractArea" to="." method="_on_interact_area_interaction_started" flags=18] +[connection signal="observers_changed" from="InteractArea" to="." method="_on_interact_area_observers_changed"] diff --git a/scenes/quests/lore_quests/quest_001/2_ink_combat/components/ink_combat_round_1.gd b/scenes/quests/lore_quests/quest_001/2_ink_combat/components/ink_combat_round_1.gd index afbe30506..e56492c62 100644 --- a/scenes/quests/lore_quests/quest_001/2_ink_combat/components/ink_combat_round_1.gd +++ b/scenes/quests/lore_quests/quest_001/2_ink_combat/components/ink_combat_round_1.gd @@ -2,6 +2,12 @@ # SPDX-License-Identifier: MPL-2.0 extends Node2D +@onready var input_hints: HBoxContainer = %InputHints + func _ready() -> void: - GameState.set_ability(Enums.PlayerAbilities.ABILITY_A, true) + input_hints.visible = false + + +func _on_repel_powerup_collected() -> void: + input_hints.visible = true diff --git a/scenes/quests/lore_quests/quest_001/2_ink_combat/ink_combat_round_1.tscn b/scenes/quests/lore_quests/quest_001/2_ink_combat/ink_combat_round_1.tscn index 2067b8b9d..babc0654e 100644 --- a/scenes/quests/lore_quests/quest_001/2_ink_combat/ink_combat_round_1.tscn +++ b/scenes/quests/lore_quests/quest_001/2_ink_combat/ink_combat_round_1.tscn @@ -25,6 +25,7 @@ [ext_resource type="AudioStream" uid="uid://bbnj2fog3rdy8" path="res://assets/first_party/sounds/throwing_enemy/Spray.wav" id="14_7ekah"] [ext_resource type="PackedScene" uid="uid://c5jedlvvnnbi0" path="res://scenes/game_elements/props/projectile/ink_blob_projectile.tscn" id="16_k65js"] [ext_resource type="PackedScene" uid="uid://dkx3dgc1br3b4" path="res://scenes/ui_elements/input_hints/repel_hint.tscn" id="22_8me22"] +[ext_resource type="PackedScene" uid="uid://dmevaymmt6wco" path="res://scenes/game_elements/props/powerup/powerup.tscn" id="26_a4lwd"] [sub_resource type="Animation" id="Animation_nlryc"] resource_name = "goal_completed" @@ -211,9 +212,16 @@ scale = Vector2(1.00063, 0.911434) position = Vector2(674, 32) scale = Vector2(0.95, 0.95) +[node name="RepelPowerup" parent="OnTheGround" unique_id=1795462717 instance=ExtResource("26_a4lwd")] +unique_name_in_owner = true +position = Vector2(236, 428) +highlight_color = Color(0, 0, 1, 1) + [node name="ScreenOverlay" type="CanvasLayer" parent="." unique_id=127644403] [node name="InputHints" type="HBoxContainer" parent="ScreenOverlay" unique_id=367368380] +unique_name_in_owner = true +visible = false anchors_preset = 2 anchor_top = 1.0 anchor_bottom = 1.0 @@ -237,3 +245,4 @@ editor_draw_limits = true [connection signal="cinematic_finished" from="Cinematic" to="FillGameLogic" method="start"] [connection signal="goal_reached" from="FillGameLogic" to="FillGameLogic/LevelCompletedAnimation" method="play" binds= ["goal_completed"]] +[connection signal="collected" from="OnTheGround/RepelPowerup" to="." method="_on_repel_powerup_collected"] diff --git a/scenes/quests/template_quests/NO_EDIT/2_NO_EDIT_combat/NO_EDIT_combat.tscn b/scenes/quests/template_quests/NO_EDIT/2_NO_EDIT_combat/NO_EDIT_combat.tscn index d0980d4d4..7554ce328 100644 --- a/scenes/quests/template_quests/NO_EDIT/2_NO_EDIT_combat/NO_EDIT_combat.tscn +++ b/scenes/quests/template_quests/NO_EDIT/2_NO_EDIT_combat/NO_EDIT_combat.tscn @@ -13,6 +13,7 @@ [ext_resource type="Script" uid="uid://bgmwplmj3bfls" path="res://scenes/globals/game_state/inventory/inventory_item.gd" id="11_4lmqk"] [ext_resource type="PackedScene" uid="uid://b82nsrh332syj" path="res://scenes/game_elements/characters/enemies/throwing_enemy/throwing_enemy.tscn" id="11_64btt"] [ext_resource type="PackedScene" uid="uid://y8ha8abfyap2" path="res://scenes/game_elements/props/filling_barrel/filling_barrel.tscn" id="12_2eyq6"] +[ext_resource type="PackedScene" uid="uid://dmevaymmt6wco" path="res://scenes/game_elements/props/powerup/powerup.tscn" id="14_6xf5p"] [ext_resource type="PackedScene" uid="uid://cfcgrfvtn04yp" path="res://scenes/ui_elements/hud/hud.tscn" id="14_ttfgd"] [sub_resource type="Resource" id="Resource_a51xm"] @@ -105,6 +106,10 @@ item = SubResource("Resource_a51xm") collected_dialogue = ExtResource("2_3qn31") dialogue_title = &"well_done" +[node name="RepelPowerup" parent="OnTheGround" unique_id=1795462717 instance=ExtResource("14_6xf5p")] +position = Vector2(114, 488) +highlight_color = Color(0, 0, 1, 1) + [node name="ScreenOverlay" type="CanvasLayer" parent="." unique_id=1277924950] [node name="HUD" parent="." unique_id=1216951978 instance=ExtResource("14_ttfgd")]